camstack 1.2.61 → 1.2.63
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-
|
|
23637
|
-
var
|
|
23638
|
-
"../system/dist/dist-
|
|
23636
|
+
// ../system/dist/dist-CDgIzo82.js
|
|
23637
|
+
var require_dist_CDgIzo82 = __commonJS({
|
|
23638
|
+
"../system/dist/dist-CDgIzo82.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -26256,29 +26256,50 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
26256
26256
|
});
|
|
26257
26257
|
var StorageMigrationLeaseInputSchema = zod.z.object({ leaseId: zod.z.string().min(1) });
|
|
26258
26258
|
var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
26259
|
+
var MediaRelocateModeSchema = zod.z.enum([
|
|
26260
|
+
"move",
|
|
26261
|
+
"seal",
|
|
26262
|
+
"gallery"
|
|
26263
|
+
]);
|
|
26259
26264
|
var RelocateMediaInputSchema = zod.z.object({
|
|
26260
26265
|
toLocationId: zod.z.string(),
|
|
26261
|
-
throttleMbps: zod.z.number().min(1).max(1e3).optional()
|
|
26266
|
+
throttleMbps: zod.z.number().min(1).max(1e3).optional(),
|
|
26267
|
+
/** Omitted = `move`, the pre-existing behaviour. */
|
|
26268
|
+
mode: MediaRelocateModeSchema.optional()
|
|
26269
|
+
});
|
|
26270
|
+
var UnstampedEventMediaCountSchema = zod.z.object({
|
|
26271
|
+
media: zod.z.number().int().nonnegative(),
|
|
26272
|
+
retrainFrames: zod.z.number().int().nonnegative(),
|
|
26273
|
+
total: zod.z.number().int().nonnegative()
|
|
26262
26274
|
});
|
|
26263
26275
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
26264
26276
|
var StorageMigrationClassSchema = zod.z.enum([
|
|
26265
26277
|
"recordings",
|
|
26266
26278
|
"recordingsLow",
|
|
26267
|
-
"eventMedia"
|
|
26279
|
+
"eventMedia",
|
|
26280
|
+
"backups",
|
|
26281
|
+
"galleryMedia"
|
|
26268
26282
|
]);
|
|
26269
26283
|
var StorageMigrationDestinationsSchema = zod.z.object({
|
|
26270
26284
|
recordings: zod.z.string().min(1).optional(),
|
|
26271
26285
|
recordingsLow: zod.z.string().min(1).optional(),
|
|
26272
|
-
eventMedia: zod.z.string().min(1).optional()
|
|
26286
|
+
eventMedia: zod.z.string().min(1).optional(),
|
|
26287
|
+
backups: zod.z.string().min(1).optional(),
|
|
26288
|
+
galleryMedia: zod.z.string().min(1).optional()
|
|
26273
26289
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
26290
|
+
var StorageMigrationModeSchema = zod.z.enum(["blocking", "nonBlocking"]);
|
|
26274
26291
|
var StorageMigrationInputSchema = zod.z.object({
|
|
26275
26292
|
destinations: StorageMigrationDestinationsSchema,
|
|
26276
|
-
throttleMbps: zod.z.number().min(1).max(1e3).optional()
|
|
26293
|
+
throttleMbps: zod.z.number().min(1).max(1e3).optional(),
|
|
26294
|
+
/** Omitted = `blocking`, which stays the default. */
|
|
26295
|
+
mode: StorageMigrationModeSchema.optional()
|
|
26277
26296
|
});
|
|
26278
26297
|
var StorageMigrationPhaseSchema = zod.z.enum([
|
|
26279
26298
|
"planning",
|
|
26299
|
+
"sealing",
|
|
26280
26300
|
"pausing",
|
|
26281
26301
|
"moving",
|
|
26302
|
+
"draining",
|
|
26282
26303
|
"verifying",
|
|
26283
26304
|
"repointing",
|
|
26284
26305
|
"refreshing",
|
|
@@ -26303,6 +26324,9 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
26303
26324
|
var StorageMigrationJobSchema = zod.z.object({
|
|
26304
26325
|
jobId: zod.z.string(),
|
|
26305
26326
|
phase: StorageMigrationPhaseSchema,
|
|
26327
|
+
/** Which order this job is running. `status` is the only place an operator
|
|
26328
|
+
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
26329
|
+
mode: StorageMigrationModeSchema,
|
|
26306
26330
|
destinations: StorageMigrationDestinationsSchema,
|
|
26307
26331
|
throttleMbps: zod.z.number(),
|
|
26308
26332
|
moves: zod.z.array(StorageMigrationMoveSchema),
|
|
@@ -26315,13 +26339,31 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
26315
26339
|
finishedAt: zod.z.number().nullable(),
|
|
26316
26340
|
error: zod.z.string().nullable()
|
|
26317
26341
|
});
|
|
26342
|
+
var StorageMigrationFindingCodeSchema = zod.z.enum([
|
|
26343
|
+
"sharesDeviceWithSource",
|
|
26344
|
+
"deviceIdentityUnknown",
|
|
26345
|
+
"unstampedEventMediaRows",
|
|
26346
|
+
"blockingOnly",
|
|
26347
|
+
"noMover"
|
|
26348
|
+
]);
|
|
26349
|
+
var StorageMigrationFindingSchema = zod.z.object({
|
|
26350
|
+
code: StorageMigrationFindingCodeSchema,
|
|
26351
|
+
storageClass: StorageMigrationClassSchema,
|
|
26352
|
+
/** Human-readable, already carrying the ids and counts. */
|
|
26353
|
+
message: zod.z.string()
|
|
26354
|
+
});
|
|
26318
26355
|
var StorageMigrationPlanSchema = zod.z.object({
|
|
26319
26356
|
destinations: StorageMigrationDestinationsSchema,
|
|
26357
|
+
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
26358
|
+
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
26359
|
+
* it. */
|
|
26360
|
+
mode: StorageMigrationModeSchema,
|
|
26320
26361
|
moves: zod.z.array(zod.z.object({
|
|
26321
26362
|
storageClass: StorageMigrationClassSchema,
|
|
26322
26363
|
fromLocationId: zod.z.string(),
|
|
26323
26364
|
toLocationId: zod.z.string()
|
|
26324
|
-
}))
|
|
26365
|
+
})),
|
|
26366
|
+
findings: zod.z.array(StorageMigrationFindingSchema)
|
|
26325
26367
|
});
|
|
26326
26368
|
var StorageLocationTypeSchema = zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
26327
26369
|
var StorageLocationSchema = zod.z.object({
|
|
@@ -37316,6 +37358,17 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
37316
37358
|
kind: "mutation",
|
|
37317
37359
|
auth: "admin"
|
|
37318
37360
|
}),
|
|
37361
|
+
/**
|
|
37362
|
+
* How many media / retrain rows still carry NO `locationId`.
|
|
37363
|
+
*
|
|
37364
|
+
* A NULL row means "wherever `eventMedia` points NOW", so the instant a
|
|
37365
|
+
* repoint moves that pointer every such row reads from the new disk while
|
|
37366
|
+
* its bytes are on the old one — the archive goes dark until a drain
|
|
37367
|
+
* happens to stamp it. This count is what the migration planner's
|
|
37368
|
+
* non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
|
|
37369
|
+
* it to zero.
|
|
37370
|
+
*/
|
|
37371
|
+
countUnstampedEventMedia: method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
|
|
37319
37372
|
/** Every relocate job this addon knows about, newest first (in RAM: the
|
|
37320
37373
|
* move is resumable, so a lost list costs nothing but the display). */
|
|
37321
37374
|
listRelocateMediaJobs: method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
|
|
@@ -39768,7 +39821,24 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
39768
39821
|
kind: "mutation",
|
|
39769
39822
|
auth: "admin"
|
|
39770
39823
|
}),
|
|
39771
|
-
|
|
39824
|
+
/**
|
|
39825
|
+
* Remove a location record. REFUSES a location that still holds data —
|
|
39826
|
+
* deleting a drained location whose durable rows still name it is how this
|
|
39827
|
+
* hub acquired 2 131 ghost `recordings:high` segments, and playback does
|
|
39828
|
+
* not stat, so the operator sees a silent black window rather than an
|
|
39829
|
+
* error.
|
|
39830
|
+
*
|
|
39831
|
+
* `force` exists because the occupancy check is BEST-EFFORT and refuses on
|
|
39832
|
+
* "unknown" as well as on "occupied" (a read that fails must not authorise
|
|
39833
|
+
* a destruction — D49). A location on a removed disk, on another node, or
|
|
39834
|
+
* behind a remote provider answers "unknown" forever, and a refusal an
|
|
39835
|
+
* operator cannot override is its own failure mode. `force: true` is
|
|
39836
|
+
* logged, loudly, with what the check saw.
|
|
39837
|
+
*/
|
|
39838
|
+
deleteLocation: method(zod.z.object({
|
|
39839
|
+
id: zod.z.string(),
|
|
39840
|
+
force: zod.z.boolean().optional()
|
|
39841
|
+
}), zod.z.void(), {
|
|
39772
39842
|
kind: "mutation",
|
|
39773
39843
|
auth: "admin"
|
|
39774
39844
|
}),
|
|
@@ -52862,6 +52932,12 @@ var require_dist_BPlfW_CG = __commonJS({
|
|
|
52862
52932
|
addonId: null,
|
|
52863
52933
|
access: "create"
|
|
52864
52934
|
},
|
|
52935
|
+
"pipelineAnalytics.countUnstampedEventMedia": {
|
|
52936
|
+
capName: "pipeline-analytics",
|
|
52937
|
+
capScope: "device",
|
|
52938
|
+
addonId: null,
|
|
52939
|
+
access: "view"
|
|
52940
|
+
},
|
|
52865
52941
|
"pipelineAnalytics.deleteDeviceEvents": {
|
|
52866
52942
|
capName: "pipeline-analytics",
|
|
52867
52943
|
capScope: "device",
|
|
@@ -58713,7 +58789,7 @@ var require_alerts_addon = __commonJS({
|
|
|
58713
58789
|
[Symbol.toStringTag]: { value: "Module" }
|
|
58714
58790
|
});
|
|
58715
58791
|
require_chunk_Cek0wNdY();
|
|
58716
|
-
var require_dist10 =
|
|
58792
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
58717
58793
|
function selectExpired(alerts, cutoffMs) {
|
|
58718
58794
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
58719
58795
|
}
|
|
@@ -59532,7 +59608,7 @@ var require_console_logging = __commonJS({
|
|
|
59532
59608
|
[Symbol.toStringTag]: { value: "Module" }
|
|
59533
59609
|
});
|
|
59534
59610
|
require_chunk_Cek0wNdY();
|
|
59535
|
-
var require_dist10 =
|
|
59611
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
59536
59612
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
59537
59613
|
var LEVEL_RANK = {
|
|
59538
59614
|
debug: 0,
|
|
@@ -59626,7 +59702,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
59626
59702
|
"use strict";
|
|
59627
59703
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
59628
59704
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
59629
|
-
var require_dist10 =
|
|
59705
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
59630
59706
|
var node_crypto = __require("crypto");
|
|
59631
59707
|
var node_fs_promises = __require("fs/promises");
|
|
59632
59708
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -60523,11 +60599,11 @@ var require_core_blocks = __commonJS({
|
|
|
60523
60599
|
}
|
|
60524
60600
|
});
|
|
60525
60601
|
|
|
60526
|
-
// ../system/dist/retired-settings-keys-
|
|
60527
|
-
var
|
|
60528
|
-
"../system/dist/retired-settings-keys-
|
|
60602
|
+
// ../system/dist/retired-settings-keys-BfAzWvPC.js
|
|
60603
|
+
var require_retired_settings_keys_BfAzWvPC = __commonJS({
|
|
60604
|
+
"../system/dist/retired-settings-keys-BfAzWvPC.js"(exports) {
|
|
60529
60605
|
"use strict";
|
|
60530
|
-
var require_dist10 =
|
|
60606
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
60531
60607
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
60532
60608
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
60533
60609
|
return raw === "" || raw === "hub";
|
|
@@ -62741,8 +62817,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
62741
62817
|
[Symbol.toStringTag]: { value: "Module" }
|
|
62742
62818
|
});
|
|
62743
62819
|
require_chunk_Cek0wNdY();
|
|
62744
|
-
var require_dist10 =
|
|
62745
|
-
var require_retired_settings_keys =
|
|
62820
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
62821
|
+
var require_retired_settings_keys = require_retired_settings_keys_BfAzWvPC();
|
|
62746
62822
|
var node_crypto = __require("crypto");
|
|
62747
62823
|
var _camstack_types_node = require_node();
|
|
62748
62824
|
var JOB_HISTORY = 20;
|
|
@@ -67491,7 +67567,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
67491
67567
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67492
67568
|
});
|
|
67493
67569
|
require_chunk_Cek0wNdY();
|
|
67494
|
-
var require_dist10 =
|
|
67570
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
67495
67571
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
67496
67572
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
67497
67573
|
var HubForwarderDestination = class {
|
|
@@ -67628,7 +67704,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
67628
67704
|
"use strict";
|
|
67629
67705
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
67630
67706
|
require_chunk_Cek0wNdY();
|
|
67631
|
-
var require_dist10 =
|
|
67707
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
67632
67708
|
var NO_DEVICES = "liveness:no-devices";
|
|
67633
67709
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
67634
67710
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -67818,7 +67894,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
67818
67894
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67819
67895
|
});
|
|
67820
67896
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
67821
|
-
var require_dist10 =
|
|
67897
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
67822
67898
|
var node_crypto = __require("crypto");
|
|
67823
67899
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
67824
67900
|
var crypto$1 = __require("crypto");
|
|
@@ -75631,7 +75707,7 @@ var require_loki_logging = __commonJS({
|
|
|
75631
75707
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75632
75708
|
});
|
|
75633
75709
|
require_chunk_Cek0wNdY();
|
|
75634
|
-
var require_dist10 =
|
|
75710
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
75635
75711
|
function sanitizeLabelName(raw) {
|
|
75636
75712
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
75637
75713
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -76196,7 +76272,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76196
76272
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76197
76273
|
});
|
|
76198
76274
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76199
|
-
var require_dist10 =
|
|
76275
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
76200
76276
|
var node_fs_promises = __require("fs/promises");
|
|
76201
76277
|
var node_child_process = __require("child_process");
|
|
76202
76278
|
var node_util = __require("util");
|
|
@@ -78818,7 +78894,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
78818
78894
|
[Symbol.toStringTag]: { value: "Module" }
|
|
78819
78895
|
});
|
|
78820
78896
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
78821
|
-
var require_dist10 =
|
|
78897
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
78822
78898
|
var node_crypto = __require("crypto");
|
|
78823
78899
|
var node_fs_promises = __require("fs/promises");
|
|
78824
78900
|
var node_path = __require("path");
|
|
@@ -79934,8 +80010,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
79934
80010
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79935
80011
|
});
|
|
79936
80012
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79937
|
-
var require_dist10 =
|
|
79938
|
-
var require_retired_settings_keys =
|
|
80013
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
80014
|
+
var require_retired_settings_keys = require_retired_settings_keys_BfAzWvPC();
|
|
79939
80015
|
var node_crypto = __require("crypto");
|
|
79940
80016
|
var node_fs = __require("fs");
|
|
79941
80017
|
var node_module = __require("module");
|
|
@@ -82214,7 +82290,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82214
82290
|
[Symbol.toStringTag]: { value: "Module" }
|
|
82215
82291
|
});
|
|
82216
82292
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
82217
|
-
var require_dist10 =
|
|
82293
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
82218
82294
|
var node_crypto = __require("crypto");
|
|
82219
82295
|
var node_fs_promises = __require("fs/promises");
|
|
82220
82296
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -82397,8 +82473,19 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82397
82473
|
var STORAGE_CLASSES = [
|
|
82398
82474
|
"recordings",
|
|
82399
82475
|
"recordingsLow",
|
|
82400
|
-
"eventMedia"
|
|
82476
|
+
"eventMedia",
|
|
82477
|
+
"backups",
|
|
82478
|
+
"galleryMedia"
|
|
82479
|
+
];
|
|
82480
|
+
var MOVER_CLASSES = [
|
|
82481
|
+
"recordings",
|
|
82482
|
+
"recordingsLow",
|
|
82483
|
+
"eventMedia",
|
|
82484
|
+
"galleryMedia"
|
|
82401
82485
|
];
|
|
82486
|
+
var BLOCKING_ONLY_CLASSES = ["galleryMedia"];
|
|
82487
|
+
var GALLERY_FORBIDDEN_NEIGHBOUR_TYPES = ["recordings", "recordingsLow"];
|
|
82488
|
+
var MOVE_LANES = ["media", "footage"];
|
|
82402
82489
|
var PARTICIPANTS = [
|
|
82403
82490
|
"pipeline",
|
|
82404
82491
|
"recorder",
|
|
@@ -82418,7 +82505,9 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82418
82505
|
this.deps = deps;
|
|
82419
82506
|
}
|
|
82420
82507
|
async plan(input) {
|
|
82508
|
+
const mode = input.mode ?? "blocking";
|
|
82421
82509
|
const moves = [];
|
|
82510
|
+
const findings = [];
|
|
82422
82511
|
for (const storageClass of STORAGE_CLASSES) {
|
|
82423
82512
|
const targetId = input.destinations[storageClass];
|
|
82424
82513
|
if (targetId === void 0) continue;
|
|
@@ -82428,6 +82517,9 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82428
82517
|
if (!target) throw new Error(`Storage location "${targetId}" not found`);
|
|
82429
82518
|
if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
|
|
82430
82519
|
if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
|
|
82520
|
+
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.`);
|
|
82521
|
+
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".`);
|
|
82522
|
+
await this.refuseGalleryOntoRecordingsVolume(storageClass, targetId);
|
|
82431
82523
|
moves.push({
|
|
82432
82524
|
storageClass,
|
|
82433
82525
|
fromLocationId: source.id,
|
|
@@ -82436,17 +82528,79 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82436
82528
|
state: null,
|
|
82437
82529
|
error: null
|
|
82438
82530
|
});
|
|
82531
|
+
if (BLOCKING_ONLY_CLASSES.includes(storageClass)) findings.push({
|
|
82532
|
+
code: "blockingOnly",
|
|
82533
|
+
storageClass,
|
|
82534
|
+
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.`
|
|
82535
|
+
});
|
|
82536
|
+
findings.push(...await this.deviceFindings(storageClass, source.id, target.id));
|
|
82439
82537
|
}
|
|
82440
82538
|
if (moves.length === 0) throw new Error("select at least one storage class");
|
|
82539
|
+
if (moves.some((move) => move.storageClass === "eventMedia")) {
|
|
82540
|
+
const unstamped = await this.deps.participants.analytics.countUnstamped();
|
|
82541
|
+
if (unstamped.total > 0) {
|
|
82542
|
+
if (mode === "nonBlocking") throw new Error(`${unstamped.total.toString()} event-media row(s) still carry no locationId (${unstamped.media.toString()} media, ${unstamped.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them. Run the seal first: pipelineAnalytics.relocateMedia({ mode: "seal", toLocationId: "${this.deps.locations.getDefaultLocation("eventMedia")?.id ?? "eventMedia"}" }).`);
|
|
82543
|
+
findings.push({
|
|
82544
|
+
code: "unstampedEventMediaRows",
|
|
82545
|
+
storageClass: "eventMedia",
|
|
82546
|
+
message: `${unstamped.total.toString()} event-media row(s) carry no locationId. This blocking migration stamps them as it moves them, but a non-blocking one would be refused until a seal pass drives the count to zero.`
|
|
82547
|
+
});
|
|
82548
|
+
}
|
|
82549
|
+
}
|
|
82441
82550
|
return {
|
|
82442
82551
|
destinations: input.destinations,
|
|
82552
|
+
mode,
|
|
82443
82553
|
moves: moves.map(({ storageClass, fromLocationId, toLocationId }) => ({
|
|
82444
82554
|
storageClass,
|
|
82445
82555
|
fromLocationId,
|
|
82446
82556
|
toLocationId
|
|
82447
|
-
}))
|
|
82557
|
+
})),
|
|
82558
|
+
findings
|
|
82448
82559
|
};
|
|
82449
82560
|
}
|
|
82561
|
+
/**
|
|
82562
|
+
* HARD refusal: `galleryMedia` onto a volume a recordings class lives on.
|
|
82563
|
+
*
|
|
82564
|
+
* This is the one same-device case that is a refusal rather than a finding,
|
|
82565
|
+
* because its consequence is silent and arrives months later — the gallery is
|
|
82566
|
+
* wiped by the next footage wipe, quota eviction or recordings disk swap, and
|
|
82567
|
+
* nothing at that moment points back at this migration.
|
|
82568
|
+
*
|
|
82569
|
+
* It can only refuse what it can SEE. `deviceKeyOf` is a realpath, so it
|
|
82570
|
+
* catches a symlink, a bind mount and a literally-shared directory — and
|
|
82571
|
+
* misses two distinct directories on one filesystem, and any location pinned
|
|
82572
|
+
* to another node. Those come back `null`/distinct and produce a
|
|
82573
|
+
* `deviceIdentityUnknown` finding instead.
|
|
82574
|
+
*/
|
|
82575
|
+
async refuseGalleryOntoRecordingsVolume(storageClass, targetId) {
|
|
82576
|
+
if (storageClass !== "galleryMedia") return;
|
|
82577
|
+
const targetKey = await this.deviceKey(targetId);
|
|
82578
|
+
if (targetKey === null) return;
|
|
82579
|
+
for (const type of GALLERY_FORBIDDEN_NEIGHBOUR_TYPES) for (const neighbour of this.deps.locations.listLocations({ type })) {
|
|
82580
|
+
if (await this.deviceKey(neighbour.id) !== targetKey) continue;
|
|
82581
|
+
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.`);
|
|
82582
|
+
}
|
|
82583
|
+
}
|
|
82584
|
+
/** Same-device observations that are reported, not refused. */
|
|
82585
|
+
async deviceFindings(storageClass, sourceId, targetId) {
|
|
82586
|
+
if (this.deps.deviceKeyOf === void 0) return [];
|
|
82587
|
+
const [sourceKey, targetKey] = await Promise.all([this.deviceKey(sourceId), this.deviceKey(targetId)]);
|
|
82588
|
+
if (sourceKey === null || targetKey === null) return [{
|
|
82589
|
+
code: "deviceIdentityUnknown",
|
|
82590
|
+
storageClass,
|
|
82591
|
+
message: `Could not establish whether "${sourceId}" and "${targetId}" are on the same device (a location with no basePath, or pinned to another node \u2014 the realpath would have to be taken there). Note also that this check is PATH identity, not st_dev: two distinct directories on one filesystem are never detected as one device.`
|
|
82592
|
+
}];
|
|
82593
|
+
if (sourceKey !== targetKey) return [];
|
|
82594
|
+
return [{
|
|
82595
|
+
code: "sharesDeviceWithSource",
|
|
82596
|
+
storageClass,
|
|
82597
|
+
message: `"${targetId}" resolves to the same path as "${sourceId}". The move will re-stamp rows rather than copy bytes, and it buys no redundancy \u2014 both "locations" are one disk.`
|
|
82598
|
+
}];
|
|
82599
|
+
}
|
|
82600
|
+
async deviceKey(locationId) {
|
|
82601
|
+
if (this.deps.deviceKeyOf === void 0) return null;
|
|
82602
|
+
return this.deps.deviceKeyOf(locationId);
|
|
82603
|
+
}
|
|
82450
82604
|
async start(input) {
|
|
82451
82605
|
if (this.startReserved) throw new Error("storage migration is already active");
|
|
82452
82606
|
this.startReserved = true;
|
|
@@ -82463,6 +82617,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82463
82617
|
const job = {
|
|
82464
82618
|
jobId: this.deps.newId(),
|
|
82465
82619
|
phase: "planning",
|
|
82620
|
+
mode: plan.mode,
|
|
82466
82621
|
destinations: input.destinations,
|
|
82467
82622
|
throttleMbps: input.throttleMbps ?? 40,
|
|
82468
82623
|
moves: plan.moves.map((move) => ({
|
|
@@ -82480,8 +82635,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82480
82635
|
finishedAt: null,
|
|
82481
82636
|
error: null
|
|
82482
82637
|
};
|
|
82483
|
-
this.active = job;
|
|
82484
82638
|
await this.persist(job);
|
|
82639
|
+
this.active = job;
|
|
82485
82640
|
this.runPromise = this.run(job);
|
|
82486
82641
|
this.runPromise;
|
|
82487
82642
|
return job.jobId;
|
|
@@ -82496,7 +82651,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82496
82651
|
}
|
|
82497
82652
|
async cancel(jobId) {
|
|
82498
82653
|
const job = await this.status(jobId);
|
|
82499
|
-
|
|
82654
|
+
const cutoverInFlight = job !== null && job.repointed && (job.phase === "refreshing" || job.phase === "resuming");
|
|
82655
|
+
if (!job || isTerminal(job) || cutoverInFlight) return false;
|
|
82500
82656
|
job.cancelRequested = true;
|
|
82501
82657
|
await this.persist(job);
|
|
82502
82658
|
await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
|
|
@@ -82527,7 +82683,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82527
82683
|
job.pauseLeaseId ??= this.deps.newId();
|
|
82528
82684
|
if (job.repointed && job.phase === "resuming") job.phase = "refreshing";
|
|
82529
82685
|
await this.persist(job);
|
|
82530
|
-
try {
|
|
82686
|
+
if (!runsUnleased(job)) try {
|
|
82531
82687
|
await this.pauseParticipants(job, true);
|
|
82532
82688
|
} catch (err) {
|
|
82533
82689
|
job.error = err instanceof Error ? err.message : String(err);
|
|
@@ -82539,14 +82695,34 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82539
82695
|
this.runPromise;
|
|
82540
82696
|
}
|
|
82541
82697
|
}
|
|
82698
|
+
/**
|
|
82699
|
+
* The state machine. Both modes run the SAME phase handlers; only the order
|
|
82700
|
+
* differs, which is the whole of the non-blocking design — no second mover,
|
|
82701
|
+
* no parallel code path, nothing that can drift between the two.
|
|
82702
|
+
*
|
|
82703
|
+
* blocking: planning → pausing → moving → verifying → repointing →
|
|
82704
|
+
* refreshing → resuming → done
|
|
82705
|
+
* nonBlocking: planning → sealing → pausing → repointing → refreshing →
|
|
82706
|
+
* resuming → draining → verifying → done
|
|
82707
|
+
*
|
|
82708
|
+
* Written as a straight-line sequence of `if (phase === X)` blocks so boot
|
|
82709
|
+
* recovery re-enters at whatever phase the durable record says, exactly as
|
|
82710
|
+
* before.
|
|
82711
|
+
*/
|
|
82542
82712
|
async run(job) {
|
|
82713
|
+
const nonBlocking = job.mode === "nonBlocking";
|
|
82543
82714
|
try {
|
|
82544
|
-
if (job.phase === "planning") await this.setPhase(job, "pausing");
|
|
82715
|
+
if (job.phase === "planning") await this.setPhase(job, nonBlocking ? "sealing" : "pausing");
|
|
82716
|
+
if (job.phase === "sealing") {
|
|
82717
|
+
await this.sealEventMedia(job);
|
|
82718
|
+
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82719
|
+
await this.setPhase(job, "pausing");
|
|
82720
|
+
}
|
|
82545
82721
|
if (job.phase === "pausing") {
|
|
82546
82722
|
job.pauseLeaseId ??= job.jobId;
|
|
82547
82723
|
await this.pauseParticipants(job);
|
|
82548
82724
|
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82549
|
-
await this.setPhase(job, "moving");
|
|
82725
|
+
await this.setPhase(job, nonBlocking ? "repointing" : "moving");
|
|
82550
82726
|
}
|
|
82551
82727
|
if (job.phase === "moving") {
|
|
82552
82728
|
await this.startOrResumeMoves(job);
|
|
@@ -82554,7 +82730,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82554
82730
|
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82555
82731
|
await this.setPhase(job, "verifying");
|
|
82556
82732
|
}
|
|
82557
|
-
if (job.phase === "verifying") {
|
|
82733
|
+
if (job.phase === "verifying" && !nonBlocking) {
|
|
82558
82734
|
await this.verifyMoves(job);
|
|
82559
82735
|
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82560
82736
|
await this.setPhase(job, "repointing");
|
|
@@ -82567,17 +82743,28 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82567
82743
|
}
|
|
82568
82744
|
if (job.phase === "refreshing") {
|
|
82569
82745
|
const leaseId = requireLease(job);
|
|
82570
|
-
if (job.moves.some((move) => move.storageClass
|
|
82571
|
-
if (job.moves.some((move) => move.storageClass === "
|
|
82746
|
+
if (job.moves.some((move) => laneOf(move.storageClass) === "footage")) await this.deps.participants.recorder.refresh(leaseId);
|
|
82747
|
+
if (job.moves.some((move) => laneOf(move.storageClass) === "media")) await this.deps.participants.analytics.refresh(leaseId);
|
|
82572
82748
|
await this.setPhase(job, "resuming");
|
|
82573
82749
|
}
|
|
82574
82750
|
if (job.phase === "resuming") {
|
|
82575
82751
|
await this.resumeParticipants(job);
|
|
82752
|
+
await this.setPhase(job, nonBlocking ? "draining" : "done");
|
|
82753
|
+
}
|
|
82754
|
+
if (job.phase === "draining") {
|
|
82755
|
+
await this.startOrResumeMoves(job);
|
|
82756
|
+
await this.waitForMoves(job);
|
|
82757
|
+
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82758
|
+
await this.setPhase(job, "verifying");
|
|
82759
|
+
}
|
|
82760
|
+
if (job.phase === "verifying" && nonBlocking) {
|
|
82761
|
+
await this.verifyMoves(job);
|
|
82762
|
+
if (job.cancelRequested) return this.finishCancelled(job);
|
|
82576
82763
|
await this.setPhase(job, "done");
|
|
82577
82764
|
}
|
|
82578
82765
|
} catch (err) {
|
|
82579
82766
|
job.error = err instanceof Error ? err.message : String(err);
|
|
82580
|
-
if (job.repointed) {
|
|
82767
|
+
if (job.repointed && job.pausedParticipants.length > 0) {
|
|
82581
82768
|
job.phase = "refreshing";
|
|
82582
82769
|
job.finishedAt = null;
|
|
82583
82770
|
await this.persist(job);
|
|
@@ -82626,21 +82813,28 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82626
82813
|
}
|
|
82627
82814
|
}
|
|
82628
82815
|
}
|
|
82816
|
+
/**
|
|
82817
|
+
* Start whatever is startable, honouring the ONE constraint the engines
|
|
82818
|
+
* impose: each is single-flight.
|
|
82819
|
+
*
|
|
82820
|
+
* Two lanes — the recorder's footage engine and post-analysis's media engine
|
|
82821
|
+
* — run in parallel with each other and strictly FIFO within themselves.
|
|
82822
|
+
* `galleryMedia` shares the media lane with `eventMedia` because it shares
|
|
82823
|
+
* the same `MediaRelocateEngine`, which refuses a second running job; starting
|
|
82824
|
+
* both would fail the second one instantly.
|
|
82825
|
+
*/
|
|
82629
82826
|
async startOrResumeMoves(job) {
|
|
82630
82827
|
if (job.cancelRequested) return;
|
|
82631
|
-
const
|
|
82632
|
-
|
|
82633
|
-
|
|
82634
|
-
|
|
82635
|
-
|
|
82636
|
-
|
|
82637
|
-
|
|
82638
|
-
|
|
82639
|
-
|
|
82640
|
-
|
|
82641
|
-
move.moverJobId = await this.startMove(job, move);
|
|
82642
|
-
await this.persist(job);
|
|
82643
|
-
return;
|
|
82828
|
+
for (const lane of MOVE_LANES) {
|
|
82829
|
+
const laneMoves = job.moves.filter((move) => laneOf(move.storageClass) === lane);
|
|
82830
|
+
for (let index = 0; index < laneMoves.length; index++) {
|
|
82831
|
+
const move = laneMoves[index];
|
|
82832
|
+
if (move === void 0 || move.moverJobId !== null) continue;
|
|
82833
|
+
if (!laneMoves.slice(0, index).every((predecessor) => predecessor.state === "done")) break;
|
|
82834
|
+
move.moverJobId = await this.startMove(job, move);
|
|
82835
|
+
await this.persist(job);
|
|
82836
|
+
break;
|
|
82837
|
+
}
|
|
82644
82838
|
}
|
|
82645
82839
|
}
|
|
82646
82840
|
async waitForMoves(job) {
|
|
@@ -82651,7 +82845,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82651
82845
|
let complete = true;
|
|
82652
82846
|
for (const [index, move] of job.moves.entries()) {
|
|
82653
82847
|
if (job.cancelRequested && move.moverJobId === null) continue;
|
|
82654
|
-
if (
|
|
82848
|
+
if (job.moves.slice(0, index).filter((predecessor) => laneOf(predecessor.storageClass) === laneOf(move.storageClass)).some((predecessor) => predecessor.state !== "done")) {
|
|
82655
82849
|
complete = false;
|
|
82656
82850
|
continue;
|
|
82657
82851
|
}
|
|
@@ -82685,30 +82879,92 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82685
82879
|
await sleep(pollMs);
|
|
82686
82880
|
}
|
|
82687
82881
|
}
|
|
82882
|
+
/**
|
|
82883
|
+
* Nail every unstamped `eventMedia`/retrain row to the location its bytes are
|
|
82884
|
+
* ALREADY on, before anything repoints. Runs with every writer up — it moves
|
|
82885
|
+
* no bytes at all.
|
|
82886
|
+
*
|
|
82887
|
+
* The gate is the RE-COUNT, not the seal job's terminal state: a seal that
|
|
82888
|
+
* failed some rows still finishes, and "finished" is not "there are none
|
|
82889
|
+
* left". A non-zero count here fails the job while nothing has been paused
|
|
82890
|
+
* and nothing has been repointed, which is the cheapest possible place to
|
|
82891
|
+
* discover it.
|
|
82892
|
+
*
|
|
82893
|
+
* The seal's own mover job id is deliberately NOT durable. It is idempotent
|
|
82894
|
+
* and cheap, so a coordinator restart mid-seal simply re-runs the whole
|
|
82895
|
+
* phase — and the count that follows is what actually decides, so a lost job
|
|
82896
|
+
* id cannot make the gate pass.
|
|
82897
|
+
*/
|
|
82898
|
+
async sealEventMedia(job) {
|
|
82899
|
+
const move = job.moves.find((candidate) => candidate.storageClass === "eventMedia");
|
|
82900
|
+
if (move === void 0) return;
|
|
82901
|
+
if ((await this.deps.participants.analytics.countUnstamped()).total > 0) {
|
|
82902
|
+
const started = await this.deps.participants.analytics.startDrain({
|
|
82903
|
+
toLocationId: move.fromLocationId,
|
|
82904
|
+
mode: "seal"
|
|
82905
|
+
});
|
|
82906
|
+
await this.waitForSeal(job, started.jobId);
|
|
82907
|
+
if (job.cancelRequested) return;
|
|
82908
|
+
}
|
|
82909
|
+
const after = await this.deps.participants.analytics.countUnstamped();
|
|
82910
|
+
if (after.total > 0) throw new Error(`event-media seal left ${after.total.toString()} row(s) without a locationId (${after.media.toString()} media, ${after.retrainFrames.toString()} retrain frames). A non-blocking cutover would silently orphan them; nothing has been paused or repointed.`);
|
|
82911
|
+
}
|
|
82912
|
+
async waitForSeal(job, moverJobId) {
|
|
82913
|
+
const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
82914
|
+
const pollMs = this.deps.pollMs ?? 250;
|
|
82915
|
+
for (; ; ) {
|
|
82916
|
+
if (job.cancelRequested) {
|
|
82917
|
+
await this.deps.participants.analytics.cancelMove(moverJobId);
|
|
82918
|
+
return;
|
|
82919
|
+
}
|
|
82920
|
+
const status = await this.deps.participants.analytics.getMove(moverJobId);
|
|
82921
|
+
if (status === null || status.state === "done" || status.state === "cancelled") return;
|
|
82922
|
+
if (status.state === "failed") throw new Error(status.error ?? "event-media seal failed");
|
|
82923
|
+
await sleep(pollMs);
|
|
82924
|
+
}
|
|
82925
|
+
}
|
|
82688
82926
|
async verifyMoves(job) {
|
|
82689
82927
|
for (const move of job.moves) if (move.state !== "done") throw new Error(`${move.storageClass} move did not complete verification`);
|
|
82690
82928
|
}
|
|
82929
|
+
/**
|
|
82930
|
+
* Arm one class's mover.
|
|
82931
|
+
*
|
|
82932
|
+
* `draining` runs after every writer has been resumed, so there is no lease
|
|
82933
|
+
* left to present — it goes through the participants' UNLEASED entry points
|
|
82934
|
+
* (`recording.relocateFootage` / `pipelineAnalytics.relocateMedia`), which
|
|
82935
|
+
* are the same engines, not a second mover.
|
|
82936
|
+
*/
|
|
82691
82937
|
async startMove(job, move) {
|
|
82692
|
-
|
|
82693
|
-
|
|
82694
|
-
|
|
82695
|
-
|
|
82696
|
-
|
|
82697
|
-
|
|
82938
|
+
const unleased = runsUnleased(job);
|
|
82939
|
+
if (laneOf(move.storageClass) === "media") {
|
|
82940
|
+
const input2 = {
|
|
82941
|
+
toLocationId: move.toLocationId,
|
|
82942
|
+
throttleMbps: job.throttleMbps,
|
|
82943
|
+
mode: move.storageClass === "galleryMedia" ? "gallery" : "move"
|
|
82944
|
+
};
|
|
82945
|
+
return (unleased ? await this.deps.participants.analytics.startDrain(input2) : await this.deps.participants.analytics.startMove({
|
|
82946
|
+
...input2,
|
|
82947
|
+
leaseId: requireLease(job)
|
|
82948
|
+
})).jobId;
|
|
82949
|
+
}
|
|
82950
|
+
const input = {
|
|
82698
82951
|
fromLocationId: move.fromLocationId,
|
|
82699
82952
|
toLocationId: move.toLocationId,
|
|
82700
|
-
footageClass: move.storageClass,
|
|
82701
|
-
throttleMbps: job.throttleMbps
|
|
82953
|
+
footageClass: move.storageClass === "recordingsLow" ? "recordingsLow" : "recordings",
|
|
82954
|
+
throttleMbps: job.throttleMbps
|
|
82955
|
+
};
|
|
82956
|
+
return (unleased ? await this.deps.participants.recorder.startDrain(input) : await this.deps.participants.recorder.startMove({
|
|
82957
|
+
...input,
|
|
82702
82958
|
leaseId: requireLease(job)
|
|
82703
82959
|
})).jobId;
|
|
82704
82960
|
}
|
|
82705
82961
|
async moveStatus(move) {
|
|
82706
82962
|
if (move.moverJobId === null) return null;
|
|
82707
|
-
return move.storageClass === "
|
|
82963
|
+
return laneOf(move.storageClass) === "media" ? this.deps.participants.analytics.getMove(move.moverJobId) : this.deps.participants.recorder.getMove(move.moverJobId);
|
|
82708
82964
|
}
|
|
82709
82965
|
async cancelMove(move) {
|
|
82710
82966
|
if (move.moverJobId === null) return;
|
|
82711
|
-
if (move.storageClass === "
|
|
82967
|
+
if (laneOf(move.storageClass) === "media") await this.deps.participants.analytics.cancelMove(move.moverJobId);
|
|
82712
82968
|
else await this.deps.participants.recorder.cancelMove(move.moverJobId);
|
|
82713
82969
|
}
|
|
82714
82970
|
participant(name) {
|
|
@@ -82737,6 +82993,13 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82737
82993
|
function isTerminal(job) {
|
|
82738
82994
|
return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
|
|
82739
82995
|
}
|
|
82996
|
+
function laneOf(storageClass) {
|
|
82997
|
+
return storageClass === "eventMedia" || storageClass === "galleryMedia" ? "media" : "footage";
|
|
82998
|
+
}
|
|
82999
|
+
function runsUnleased(job) {
|
|
83000
|
+
if (job.phase === "sealing" || job.phase === "draining") return true;
|
|
83001
|
+
return job.phase === "verifying" && job.mode === "nonBlocking";
|
|
83002
|
+
}
|
|
82740
83003
|
function buildStorageLocationRegistry(perAddon) {
|
|
82741
83004
|
const map = /* @__PURE__ */ new Map();
|
|
82742
83005
|
for (const addonDeclarations of perAddon) for (const declaration of addonDeclarations) {
|
|
@@ -82842,6 +83105,14 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82842
83105
|
* early-boot path that predates the resolver wiring.
|
|
82843
83106
|
*/
|
|
82844
83107
|
nodeLocalResolver = null;
|
|
83108
|
+
/**
|
|
83109
|
+
* Injected occupancy probe (see {@link LocationOccupancyProbe}). `null` until
|
|
83110
|
+
* {@link setOccupancyProbe} runs, and a `null` probe answers `unknown` — so a
|
|
83111
|
+
* service with no probe REFUSES an unforced delete rather than performing one
|
|
83112
|
+
* it could not check. The check is the point; a check that defaults to
|
|
83113
|
+
* "permit" is a paragraph, not a guard.
|
|
83114
|
+
*/
|
|
83115
|
+
occupancyProbe = null;
|
|
82845
83116
|
/** `localNodeId` with any forked-child `/addon` suffix stripped. */
|
|
82846
83117
|
localNode;
|
|
82847
83118
|
/**
|
|
@@ -82941,6 +83212,15 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
82941
83212
|
this.nodeLocalResolver = resolver;
|
|
82942
83213
|
}
|
|
82943
83214
|
/**
|
|
83215
|
+
* Inject the "does this location still hold anything" probe read by
|
|
83216
|
+
* {@link deleteLocation}. Absent → every delete sees `unknown` and therefore
|
|
83217
|
+
* REFUSES without `force`, which is the safe direction for a service wired
|
|
83218
|
+
* without one.
|
|
83219
|
+
*/
|
|
83220
|
+
setOccupancyProbe(probe) {
|
|
83221
|
+
this.occupancyProbe = probe;
|
|
83222
|
+
}
|
|
83223
|
+
/**
|
|
82944
83224
|
* The full set of addon-declared storage locations, as aggregated by
|
|
82945
83225
|
* the kernel and injected via {@link setRegistry}. Powers the admin-UI
|
|
82946
83226
|
* Data screen's per-declaration grouping. Empty until the registry is
|
|
@@ -83164,13 +83444,22 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83164
83444
|
* Persistence (Task 6) mirrors the delete asynchronously, with errors
|
|
83165
83445
|
* routed to the logger — see `upsertLocation` for the rationale.
|
|
83166
83446
|
*/
|
|
83167
|
-
deleteLocation(id) {
|
|
83447
|
+
async deleteLocation(id, options) {
|
|
83168
83448
|
const loc = this.locations.get(id);
|
|
83169
83449
|
if (!loc) throw new Error(`Storage location "${id}" not found`);
|
|
83170
83450
|
if (loc.isSystem) throw new Error(`Storage location "${id}" is system-managed and cannot be deleted. Edit its config (path / providerId) instead.`);
|
|
83171
83451
|
if (loc.isDefault) {
|
|
83172
83452
|
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`);
|
|
83173
83453
|
}
|
|
83454
|
+
const occupancy = await this.probeOccupancy(loc);
|
|
83455
|
+
if (occupancy !== "empty") {
|
|
83456
|
+
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`);
|
|
83457
|
+
this.logger.warn("storage-orchestrator: FORCED delete of a location that was not verified empty", { meta: {
|
|
83458
|
+
id,
|
|
83459
|
+
type: loc.type,
|
|
83460
|
+
occupancy
|
|
83461
|
+
} });
|
|
83462
|
+
}
|
|
83174
83463
|
this.locations.delete(id);
|
|
83175
83464
|
if (this.locationStore) this.locationStore.delete(id).catch((err) => {
|
|
83176
83465
|
this.logger.error("storage-orchestrator: delete persistence failed", { meta: {
|
|
@@ -83180,6 +83469,23 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83180
83469
|
});
|
|
83181
83470
|
}
|
|
83182
83471
|
/**
|
|
83472
|
+
* Run the injected occupancy probe, converting "no probe" and "the probe
|
|
83473
|
+
* threw" into `unknown` rather than into a permit.
|
|
83474
|
+
*/
|
|
83475
|
+
async probeOccupancy(location) {
|
|
83476
|
+
const probe = this.occupancyProbe;
|
|
83477
|
+
if (probe === null) return "unknown";
|
|
83478
|
+
try {
|
|
83479
|
+
return await probe(location);
|
|
83480
|
+
} catch (err) {
|
|
83481
|
+
this.logger.warn("storage-orchestrator: occupancy probe failed", { meta: {
|
|
83482
|
+
id: location.id,
|
|
83483
|
+
error: err instanceof Error ? err.message : String(err)
|
|
83484
|
+
} });
|
|
83485
|
+
return "unknown";
|
|
83486
|
+
}
|
|
83487
|
+
}
|
|
83488
|
+
/**
|
|
83183
83489
|
* Remove system-seeded locations whose type is no longer declared by any
|
|
83184
83490
|
* addon (stale defaults from a removed location type). Operator-added
|
|
83185
83491
|
* (non-system) locations of an undeclared type are KEPT but warned — the
|
|
@@ -83575,6 +83881,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83575
83881
|
});
|
|
83576
83882
|
this.service = service;
|
|
83577
83883
|
service.setNodeLocalResolver((providerId) => this.nodeLocalByProvider.get(providerId));
|
|
83884
|
+
service.setOccupancyProbe((location) => this.locationOccupancy(location));
|
|
83578
83885
|
await service.initialize();
|
|
83579
83886
|
const provider = {
|
|
83580
83887
|
listLocations: async ({ type }) => {
|
|
@@ -83587,8 +83894,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83587
83894
|
getDefaultLocation: async ({ type }) => service.getDefaultLocation(type),
|
|
83588
83895
|
listLocationDeclarations: async () => service.listDeclarations(),
|
|
83589
83896
|
upsertLocation: async (input) => service.upsertLocation(input),
|
|
83590
|
-
deleteLocation: async ({ id }) => {
|
|
83591
|
-
service.deleteLocation(id);
|
|
83897
|
+
deleteLocation: async ({ id, force }) => {
|
|
83898
|
+
await service.deleteLocation(id, { force: force === true });
|
|
83592
83899
|
},
|
|
83593
83900
|
testLocation: async ({ id }) => {
|
|
83594
83901
|
const loc = service.getLocationById(id);
|
|
@@ -83751,6 +84058,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83751
84058
|
await this.ctx.api.recording.resumeForStorageMigration.mutate({ leaseId });
|
|
83752
84059
|
},
|
|
83753
84060
|
startMove: (input) => this.ctx.api.recording.startStorageMigrationMove.mutate(input),
|
|
84061
|
+
startDrain: (input) => this.ctx.api.recording.relocateFootage.mutate(input),
|
|
83754
84062
|
getMove: (jobId) => this.ctx.api.recording.getStorageMigrationMoveStatus.query({ jobId }),
|
|
83755
84063
|
cancelMove: async (jobId) => (await this.ctx.api.recording.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
|
|
83756
84064
|
refresh: async (leaseId) => {
|
|
@@ -83765,6 +84073,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83765
84073
|
await this.ctx.api.pipelineAnalytics.resumeForStorageMigration.mutate({ leaseId });
|
|
83766
84074
|
},
|
|
83767
84075
|
startMove: (input) => this.ctx.api.pipelineAnalytics.startStorageMigrationMove.mutate(input),
|
|
84076
|
+
startDrain: (input) => this.ctx.api.pipelineAnalytics.relocateMedia.mutate(input),
|
|
84077
|
+
countUnstamped: () => this.ctx.api.pipelineAnalytics.countUnstampedEventMedia.query({}),
|
|
83768
84078
|
getMove: (jobId) => this.ctx.api.pipelineAnalytics.getStorageMigrationMoveStatus.query({ jobId }),
|
|
83769
84079
|
cancelMove: async (jobId) => (await this.ctx.api.pipelineAnalytics.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
|
|
83770
84080
|
refresh: async (leaseId) => {
|
|
@@ -83773,7 +84083,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83773
84083
|
}
|
|
83774
84084
|
},
|
|
83775
84085
|
now: () => Date.now(),
|
|
83776
|
-
newId: () => (0, node_crypto.randomUUID)()
|
|
84086
|
+
newId: () => (0, node_crypto.randomUUID)(),
|
|
84087
|
+
deviceKeyOf: (locationId) => this.locationDeviceKey(locationId)
|
|
83777
84088
|
});
|
|
83778
84089
|
this.migration = migration;
|
|
83779
84090
|
const migrationProvider = {
|
|
@@ -83988,6 +84299,44 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
83988
84299
|
return node_path.resolve(bp);
|
|
83989
84300
|
}
|
|
83990
84301
|
}
|
|
84302
|
+
/**
|
|
84303
|
+
* Does this location still hold anything? A SHALLOW `readdir` of its
|
|
84304
|
+
* `basePath` — one syscall, never a walk. `/recordings` holds one entry per
|
|
84305
|
+
* camera, so this is cheap on a volume with millions of segments, and a
|
|
84306
|
+
* `find`-style walk of a saturated recordings disk is precisely what must
|
|
84307
|
+
* not happen here.
|
|
84308
|
+
*
|
|
84309
|
+
* **What it cannot see, stated rather than papered over:**
|
|
84310
|
+
*
|
|
84311
|
+
* - A location on ANOTHER node, or behind a remote provider (S3 / SFTP /
|
|
84312
|
+
* WebDAV): its bytes are not on this filesystem and this probe answers
|
|
84313
|
+
* `unknown`, never `empty`.
|
|
84314
|
+
* - A location with no `basePath` — same answer.
|
|
84315
|
+
* - The difference between "holds live footage" and "holds one stray
|
|
84316
|
+
* lock-file". It measures BYTES ON DISK, not durable rows, so it refuses
|
|
84317
|
+
* a location holding orphan files that no index names. That direction is
|
|
84318
|
+
* the safe one, and `force` is the escape hatch for it.
|
|
84319
|
+
*
|
|
84320
|
+
* A missing root (`ENOENT`) is `empty`, not `unknown`: an unmounted disk and
|
|
84321
|
+
* a deleted directory are indistinguishable here, and if the record's own
|
|
84322
|
+
* path does not exist there is nothing this delete can strand. Every OTHER
|
|
84323
|
+
* errno — `EACCES`, `EIO`, a stale NFS handle — is `unknown` and refuses.
|
|
84324
|
+
*/
|
|
84325
|
+
async locationOccupancy(location) {
|
|
84326
|
+
if ((location.nodeId === void 0 || location.nodeId === "" ? HUB_NODE_ID : location.nodeId) !== (this.service?.getLocalNodeId() ?? HUB_NODE_ID)) return "unknown";
|
|
84327
|
+
const basePath = this.locationBasePath(location.id);
|
|
84328
|
+
if (basePath === null) return "unknown";
|
|
84329
|
+
try {
|
|
84330
|
+
return (await node_fs_promises.readdir(basePath)).length > 0 ? "occupied" : "empty";
|
|
84331
|
+
} catch (err) {
|
|
84332
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") return "empty";
|
|
84333
|
+
this.ctx.logger.warn("storage-orchestrator: could not read a location root to check it", { meta: {
|
|
84334
|
+
id: location.id,
|
|
84335
|
+
error: err instanceof Error ? err.message : String(err)
|
|
84336
|
+
} });
|
|
84337
|
+
return "unknown";
|
|
84338
|
+
}
|
|
84339
|
+
}
|
|
83991
84340
|
/** Free capacity (%) on a location's volume via `statfs`; 100 (guard inert) when unstattable. */
|
|
83992
84341
|
async locationFreePercent(locationId) {
|
|
83993
84342
|
const bp = this.locationBasePath(locationId);
|
|
@@ -84095,7 +84444,7 @@ var require_system_config_addon = __commonJS({
|
|
|
84095
84444
|
[Symbol.toStringTag]: { value: "Module" }
|
|
84096
84445
|
});
|
|
84097
84446
|
require_chunk_Cek0wNdY();
|
|
84098
|
-
var require_dist10 =
|
|
84447
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
84099
84448
|
var SECTION_TITLES = {
|
|
84100
84449
|
server: "Server",
|
|
84101
84450
|
auth: "Authentication"
|
|
@@ -102156,7 +102505,7 @@ var require_winston_logging = __commonJS({
|
|
|
102156
102505
|
[Symbol.toStringTag]: { value: "Module" }
|
|
102157
102506
|
});
|
|
102158
102507
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
102159
|
-
var require_dist10 =
|
|
102508
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
102160
102509
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
102161
102510
|
var node_path = __require("path");
|
|
102162
102511
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -104099,9 +104448,9 @@ var require_event_category_BaEgqJNv = __commonJS({
|
|
|
104099
104448
|
}
|
|
104100
104449
|
});
|
|
104101
104450
|
|
|
104102
|
-
// ../types/dist/sleep-
|
|
104103
|
-
var
|
|
104104
|
-
"../types/dist/sleep-
|
|
104451
|
+
// ../types/dist/sleep-CJrvRDlD.js
|
|
104452
|
+
var require_sleep_CJrvRDlD = __commonJS({
|
|
104453
|
+
"../types/dist/sleep-CJrvRDlD.js"(exports) {
|
|
104105
104454
|
"use strict";
|
|
104106
104455
|
var require_event_category = require_event_category_BaEgqJNv();
|
|
104107
104456
|
var zod = require_zod();
|
|
@@ -106801,6 +107150,7 @@ var require_sleep_DUxF5DdC = __commonJS({
|
|
|
106801
107150
|
getStorageMigrationMoveStatus: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getStorageMigrationMoveStatus", "query", input),
|
|
106802
107151
|
cancelStorageMigrationMove: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "cancelStorageMigrationMove", "mutation", input),
|
|
106803
107152
|
relocateMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "relocateMedia", "mutation", input),
|
|
107153
|
+
countUnstampedEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "countUnstampedEventMedia", "query", input),
|
|
106804
107154
|
listRelocateMediaJobs: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRelocateMediaJobs", "query", input),
|
|
106805
107155
|
cancelRelocateMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "cancelRelocateMedia", "mutation", input),
|
|
106806
107156
|
listOpsLog: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listOpsLog", "query", input),
|
|
@@ -107765,7 +108115,7 @@ var require_addon = __commonJS({
|
|
|
107765
108115
|
"use strict";
|
|
107766
108116
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
107767
108117
|
var require_event_category = require_event_category_BaEgqJNv();
|
|
107768
|
-
var require_sleep =
|
|
108118
|
+
var require_sleep = require_sleep_CJrvRDlD();
|
|
107769
108119
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
107770
108120
|
var CAP_INPUT_DEFAULTS = Object.freeze({
|
|
107771
108121
|
"addons": { "getLogs": { "limit": 100 } },
|
|
@@ -114644,12 +114994,12 @@ var require_dist2 = __commonJS({
|
|
|
114644
114994
|
}
|
|
114645
114995
|
});
|
|
114646
114996
|
|
|
114647
|
-
// ../system/dist/manifest-python-deps-
|
|
114648
|
-
var
|
|
114649
|
-
"../system/dist/manifest-python-deps-
|
|
114997
|
+
// ../system/dist/manifest-python-deps-DVODn-qc.js
|
|
114998
|
+
var require_manifest_python_deps_DVODn_qc = __commonJS({
|
|
114999
|
+
"../system/dist/manifest-python-deps-DVODn-qc.js"(exports) {
|
|
114650
115000
|
"use strict";
|
|
114651
115001
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
114652
|
-
|
|
115002
|
+
require_dist_CDgIzo82();
|
|
114653
115003
|
var node_crypto = __require("crypto");
|
|
114654
115004
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
114655
115005
|
var _camstack_types_node = require_node();
|
|
@@ -126390,7 +126740,7 @@ var require_dist3 = __commonJS({
|
|
|
126390
126740
|
"use strict";
|
|
126391
126741
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
126392
126742
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
126393
|
-
var require_dist10 =
|
|
126743
|
+
var require_dist10 = require_dist_CDgIzo82();
|
|
126394
126744
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
126395
126745
|
require_alerts();
|
|
126396
126746
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -126416,7 +126766,7 @@ var require_dist3 = __commonJS({
|
|
|
126416
126766
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
126417
126767
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
126418
126768
|
var require_tls$1 = require_tls_BxQlomxd();
|
|
126419
|
-
var require_manifest_python_deps =
|
|
126769
|
+
var require_manifest_python_deps = require_manifest_python_deps_DVODn_qc();
|
|
126420
126770
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
126421
126771
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
126422
126772
|
var zod = require_zod();
|
|
@@ -135078,20 +135428,63 @@ var require_dist3 = __commonJS({
|
|
|
135078
135428
|
if (this.settingsStore === null) return;
|
|
135079
135429
|
this.settingsStore.clearDeviceRuntimeState(deviceId);
|
|
135080
135430
|
}
|
|
135431
|
+
/**
|
|
135432
|
+
* The addon's `ctx.settings`.
|
|
135433
|
+
*
|
|
135434
|
+
* The backend is chosen on EVERY call, never frozen when the view is built.
|
|
135435
|
+
* That is not a nicety: `settings-store` is owned by `storage-orchestrator`,
|
|
135436
|
+
* so the door is wired by `registerProvider` STRICTLY AFTER that addon's
|
|
135437
|
+
* context — and therefore its settings view — already exists. A view bound at
|
|
135438
|
+
* construction handed the door's own owner the sync-store backend, whose
|
|
135439
|
+
* writes throw for the whole life of the process once `sqlite-settings` runs
|
|
135440
|
+
* isolated (D181: no sync `ISettingsStore` reaches hub-main, only the async
|
|
135441
|
+
* door). Reads were worse than the writes: they answered `{}` in silence.
|
|
135442
|
+
* That is what made `storageMigration.start` fail on its first durable write
|
|
135443
|
+
* while every other addon's persistence worked. See D294.
|
|
135444
|
+
*/
|
|
135081
135445
|
createSettingsView(addonId) {
|
|
135082
|
-
|
|
135446
|
+
const storeBacked = this.createStoreSettingsView(addonId);
|
|
135447
|
+
let doorBacked = null;
|
|
135448
|
+
let doorBackedSource = null;
|
|
135449
|
+
const active = () => {
|
|
135450
|
+
if (this.settingsStore !== null) return storeBacked;
|
|
135083
135451
|
const door = this.settingsDoor;
|
|
135084
|
-
|
|
135085
|
-
|
|
135086
|
-
|
|
135087
|
-
|
|
135088
|
-
|
|
135089
|
-
|
|
135090
|
-
|
|
135091
|
-
|
|
135092
|
-
|
|
135093
|
-
|
|
135094
|
-
|
|
135452
|
+
if (door === null) return storeBacked;
|
|
135453
|
+
if (doorBacked === null || doorBackedSource !== door) {
|
|
135454
|
+
doorBackedSource = door;
|
|
135455
|
+
doorBacked = this.createDoorBackedSettingsView(addonId, door);
|
|
135456
|
+
}
|
|
135457
|
+
return doorBacked;
|
|
135458
|
+
};
|
|
135459
|
+
return {
|
|
135460
|
+
readAddonStore: () => active().readAddonStore(),
|
|
135461
|
+
writeAddonStore: (patch) => active().writeAddonStore(patch),
|
|
135462
|
+
readDeviceStore: (deviceId) => active().readDeviceStore(deviceId),
|
|
135463
|
+
readDeviceStoreBatch: (deviceIds) => active().readDeviceStoreBatch(deviceIds),
|
|
135464
|
+
writeDeviceStore: (deviceId, patch) => active().writeDeviceStore(deviceId, patch),
|
|
135465
|
+
clearDeviceStore: (deviceId) => active().clearDeviceStore(deviceId),
|
|
135466
|
+
getSection: (section) => active().getSection(section),
|
|
135467
|
+
setSection: (section, patch) => active().setSection(section, patch),
|
|
135468
|
+
readDeviceRuntimeState: (deviceId) => active().readDeviceRuntimeState(deviceId),
|
|
135469
|
+
writeDeviceRuntimeState: (deviceId, data) => active().writeDeviceRuntimeState(deviceId, data),
|
|
135470
|
+
clearDeviceRuntimeState: (deviceId) => active().clearDeviceRuntimeState(deviceId)
|
|
135471
|
+
};
|
|
135472
|
+
}
|
|
135473
|
+
/** The async-door backend of {@link createSettingsView}. */
|
|
135474
|
+
createDoorBackedSettingsView(addonId, door) {
|
|
135475
|
+
return createDoorSettingsView(addonId, door, {
|
|
135476
|
+
getSection: (section) => this.getSection(section),
|
|
135477
|
+
setSection: async (section, patch) => {
|
|
135478
|
+
for (const [key, value] of Object.entries(patch)) await door.set({
|
|
135479
|
+
collection: "system-settings",
|
|
135480
|
+
key: `${section}.${key}`,
|
|
135481
|
+
value
|
|
135482
|
+
});
|
|
135483
|
+
}
|
|
135484
|
+
});
|
|
135485
|
+
}
|
|
135486
|
+
/** The sync-`ISettingsStore` backend of {@link createSettingsView}. */
|
|
135487
|
+
createStoreSettingsView(addonId) {
|
|
135095
135488
|
const cm = this;
|
|
135096
135489
|
return {
|
|
135097
135490
|
async readAddonStore() {
|
|
@@ -135107,6 +135500,15 @@ var require_dist3 = __commonJS({
|
|
|
135107
135500
|
async readDeviceStore(deviceId) {
|
|
135108
135501
|
return cm.getAddonDevice(addonId, String(deviceId));
|
|
135109
135502
|
},
|
|
135503
|
+
async readDeviceStoreBatch(deviceIds) {
|
|
135504
|
+
const out = /* @__PURE__ */ new Map();
|
|
135505
|
+
for (const deviceId of deviceIds) try {
|
|
135506
|
+
out.set(deviceId, cm.getAddonDevice(addonId, String(deviceId)));
|
|
135507
|
+
} catch {
|
|
135508
|
+
out.set(deviceId, {});
|
|
135509
|
+
}
|
|
135510
|
+
return out;
|
|
135511
|
+
},
|
|
135110
135512
|
async writeDeviceStore(deviceId, patch) {
|
|
135111
135513
|
const key = String(deviceId);
|
|
135112
135514
|
const existing = cm.getAddonDevice(addonId, key);
|
|
@@ -207165,7 +207567,7 @@ var require_dist4 = __commonJS({
|
|
|
207165
207567
|
"use strict";
|
|
207166
207568
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
207167
207569
|
var require_event_category = require_event_category_BaEgqJNv();
|
|
207168
|
-
var require_sleep =
|
|
207570
|
+
var require_sleep = require_sleep_CJrvRDlD();
|
|
207169
207571
|
var require_canonical_hash = require_canonical_hash_DNV8S5ET();
|
|
207170
207572
|
var require_enums2 = require_enums();
|
|
207171
207573
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
@@ -208486,29 +208888,50 @@ var require_dist4 = __commonJS({
|
|
|
208486
208888
|
});
|
|
208487
208889
|
var StorageMigrationLeaseInputSchema = zod.z.object({ leaseId: zod.z.string().min(1) });
|
|
208488
208890
|
var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
208891
|
+
var MediaRelocateModeSchema = zod.z.enum([
|
|
208892
|
+
"move",
|
|
208893
|
+
"seal",
|
|
208894
|
+
"gallery"
|
|
208895
|
+
]);
|
|
208489
208896
|
var RelocateMediaInputSchema = zod.z.object({
|
|
208490
208897
|
toLocationId: zod.z.string(),
|
|
208491
|
-
throttleMbps: zod.z.number().min(1).max(1e3).optional()
|
|
208898
|
+
throttleMbps: zod.z.number().min(1).max(1e3).optional(),
|
|
208899
|
+
/** Omitted = `move`, the pre-existing behaviour. */
|
|
208900
|
+
mode: MediaRelocateModeSchema.optional()
|
|
208901
|
+
});
|
|
208902
|
+
var UnstampedEventMediaCountSchema = zod.z.object({
|
|
208903
|
+
media: zod.z.number().int().nonnegative(),
|
|
208904
|
+
retrainFrames: zod.z.number().int().nonnegative(),
|
|
208905
|
+
total: zod.z.number().int().nonnegative()
|
|
208492
208906
|
});
|
|
208493
208907
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: zod.z.string().min(1) });
|
|
208494
208908
|
var StorageMigrationClassSchema = zod.z.enum([
|
|
208495
208909
|
"recordings",
|
|
208496
208910
|
"recordingsLow",
|
|
208497
|
-
"eventMedia"
|
|
208911
|
+
"eventMedia",
|
|
208912
|
+
"backups",
|
|
208913
|
+
"galleryMedia"
|
|
208498
208914
|
]);
|
|
208499
208915
|
var StorageMigrationDestinationsSchema = zod.z.object({
|
|
208500
208916
|
recordings: zod.z.string().min(1).optional(),
|
|
208501
208917
|
recordingsLow: zod.z.string().min(1).optional(),
|
|
208502
|
-
eventMedia: zod.z.string().min(1).optional()
|
|
208918
|
+
eventMedia: zod.z.string().min(1).optional(),
|
|
208919
|
+
backups: zod.z.string().min(1).optional(),
|
|
208920
|
+
galleryMedia: zod.z.string().min(1).optional()
|
|
208503
208921
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
208922
|
+
var StorageMigrationModeSchema = zod.z.enum(["blocking", "nonBlocking"]);
|
|
208504
208923
|
var StorageMigrationInputSchema = zod.z.object({
|
|
208505
208924
|
destinations: StorageMigrationDestinationsSchema,
|
|
208506
|
-
throttleMbps: zod.z.number().min(1).max(1e3).optional()
|
|
208925
|
+
throttleMbps: zod.z.number().min(1).max(1e3).optional(),
|
|
208926
|
+
/** Omitted = `blocking`, which stays the default. */
|
|
208927
|
+
mode: StorageMigrationModeSchema.optional()
|
|
208507
208928
|
});
|
|
208508
208929
|
var StorageMigrationPhaseSchema = zod.z.enum([
|
|
208509
208930
|
"planning",
|
|
208931
|
+
"sealing",
|
|
208510
208932
|
"pausing",
|
|
208511
208933
|
"moving",
|
|
208934
|
+
"draining",
|
|
208512
208935
|
"verifying",
|
|
208513
208936
|
"repointing",
|
|
208514
208937
|
"refreshing",
|
|
@@ -208533,6 +208956,9 @@ var require_dist4 = __commonJS({
|
|
|
208533
208956
|
var StorageMigrationJobSchema = zod.z.object({
|
|
208534
208957
|
jobId: zod.z.string(),
|
|
208535
208958
|
phase: StorageMigrationPhaseSchema,
|
|
208959
|
+
/** Which order this job is running. `status` is the only place an operator
|
|
208960
|
+
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
208961
|
+
mode: StorageMigrationModeSchema,
|
|
208536
208962
|
destinations: StorageMigrationDestinationsSchema,
|
|
208537
208963
|
throttleMbps: zod.z.number(),
|
|
208538
208964
|
moves: zod.z.array(StorageMigrationMoveSchema),
|
|
@@ -208545,13 +208971,31 @@ var require_dist4 = __commonJS({
|
|
|
208545
208971
|
finishedAt: zod.z.number().nullable(),
|
|
208546
208972
|
error: zod.z.string().nullable()
|
|
208547
208973
|
});
|
|
208974
|
+
var StorageMigrationFindingCodeSchema = zod.z.enum([
|
|
208975
|
+
"sharesDeviceWithSource",
|
|
208976
|
+
"deviceIdentityUnknown",
|
|
208977
|
+
"unstampedEventMediaRows",
|
|
208978
|
+
"blockingOnly",
|
|
208979
|
+
"noMover"
|
|
208980
|
+
]);
|
|
208981
|
+
var StorageMigrationFindingSchema = zod.z.object({
|
|
208982
|
+
code: StorageMigrationFindingCodeSchema,
|
|
208983
|
+
storageClass: StorageMigrationClassSchema,
|
|
208984
|
+
/** Human-readable, already carrying the ids and counts. */
|
|
208985
|
+
message: zod.z.string()
|
|
208986
|
+
});
|
|
208548
208987
|
var StorageMigrationPlanSchema = zod.z.object({
|
|
208549
208988
|
destinations: StorageMigrationDestinationsSchema,
|
|
208989
|
+
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
208990
|
+
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
208991
|
+
* it. */
|
|
208992
|
+
mode: StorageMigrationModeSchema,
|
|
208550
208993
|
moves: zod.z.array(zod.z.object({
|
|
208551
208994
|
storageClass: StorageMigrationClassSchema,
|
|
208552
208995
|
fromLocationId: zod.z.string(),
|
|
208553
208996
|
toLocationId: zod.z.string()
|
|
208554
|
-
}))
|
|
208997
|
+
})),
|
|
208998
|
+
findings: zod.z.array(StorageMigrationFindingSchema)
|
|
208555
208999
|
});
|
|
208556
209000
|
var SUB_DETECTION_TYPES = ["face", "plate"];
|
|
208557
209001
|
var RECOGNITION_TYPES = [
|
|
@@ -221527,6 +221971,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
221527
221971
|
kind: "mutation",
|
|
221528
221972
|
auth: "admin"
|
|
221529
221973
|
}),
|
|
221974
|
+
/**
|
|
221975
|
+
* How many media / retrain rows still carry NO `locationId`.
|
|
221976
|
+
*
|
|
221977
|
+
* A NULL row means "wherever `eventMedia` points NOW", so the instant a
|
|
221978
|
+
* repoint moves that pointer every such row reads from the new disk while
|
|
221979
|
+
* its bytes are on the old one — the archive goes dark until a drain
|
|
221980
|
+
* happens to stamp it. This count is what the migration planner's
|
|
221981
|
+
* non-blocking gate reads; `relocateMedia({ mode: 'seal' })` is what drives
|
|
221982
|
+
* it to zero.
|
|
221983
|
+
*/
|
|
221984
|
+
countUnstampedEventMedia: require_sleep.method(zod.z.object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
|
|
221530
221985
|
/** Every relocate job this addon knows about, newest first (in RAM: the
|
|
221531
221986
|
* move is resumable, so a lost list costs nothing but the display). */
|
|
221532
221987
|
listRelocateMediaJobs: require_sleep.method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
|
|
@@ -224143,7 +224598,24 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
224143
224598
|
kind: "mutation",
|
|
224144
224599
|
auth: "admin"
|
|
224145
224600
|
}),
|
|
224146
|
-
|
|
224601
|
+
/**
|
|
224602
|
+
* Remove a location record. REFUSES a location that still holds data —
|
|
224603
|
+
* deleting a drained location whose durable rows still name it is how this
|
|
224604
|
+
* hub acquired 2 131 ghost `recordings:high` segments, and playback does
|
|
224605
|
+
* not stat, so the operator sees a silent black window rather than an
|
|
224606
|
+
* error.
|
|
224607
|
+
*
|
|
224608
|
+
* `force` exists because the occupancy check is BEST-EFFORT and refuses on
|
|
224609
|
+
* "unknown" as well as on "occupied" (a read that fails must not authorise
|
|
224610
|
+
* a destruction — D49). A location on a removed disk, on another node, or
|
|
224611
|
+
* behind a remote provider answers "unknown" forever, and a refusal an
|
|
224612
|
+
* operator cannot override is its own failure mode. `force: true` is
|
|
224613
|
+
* logged, loudly, with what the check saw.
|
|
224614
|
+
*/
|
|
224615
|
+
deleteLocation: require_sleep.method(zod.z.object({
|
|
224616
|
+
id: zod.z.string(),
|
|
224617
|
+
force: zod.z.boolean().optional()
|
|
224618
|
+
}), zod.z.void(), {
|
|
224147
224619
|
kind: "mutation",
|
|
224148
224620
|
auth: "admin"
|
|
224149
224621
|
}),
|
|
@@ -240182,6 +240654,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
240182
240654
|
addonId: null,
|
|
240183
240655
|
access: "create"
|
|
240184
240656
|
},
|
|
240657
|
+
"pipelineAnalytics.countUnstampedEventMedia": {
|
|
240658
|
+
capName: "pipeline-analytics",
|
|
240659
|
+
capScope: "device",
|
|
240660
|
+
addonId: null,
|
|
240661
|
+
access: "view"
|
|
240662
|
+
},
|
|
240185
240663
|
"pipelineAnalytics.deleteDeviceEvents": {
|
|
240186
240664
|
capName: "pipeline-analytics",
|
|
240187
240665
|
capScope: "device",
|
|
@@ -249205,6 +249683,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
249205
249683
|
exports.MediaPlayerRepeatSchema = MediaPlayerRepeatSchema;
|
|
249206
249684
|
exports.MediaPlayerStateSchema = MediaPlayerStateSchema;
|
|
249207
249685
|
exports.MediaPlayerStatusSchema = MediaPlayerStatusSchema;
|
|
249686
|
+
exports.MediaRelocateModeSchema = MediaRelocateModeSchema;
|
|
249208
249687
|
exports.MeshPeerSchema = MeshPeerSchema;
|
|
249209
249688
|
exports.MeshStatusSchema = MeshStatusSchema;
|
|
249210
249689
|
exports.MethodAccessSchema = MethodAccessSchema;
|
|
@@ -249582,11 +250061,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
249582
250061
|
exports.StorageLocationTypeSchema = StorageLocationTypeSchema;
|
|
249583
250062
|
exports.StorageMigrationClassSchema = StorageMigrationClassSchema;
|
|
249584
250063
|
exports.StorageMigrationDestinationsSchema = StorageMigrationDestinationsSchema;
|
|
250064
|
+
exports.StorageMigrationFindingCodeSchema = StorageMigrationFindingCodeSchema;
|
|
250065
|
+
exports.StorageMigrationFindingSchema = StorageMigrationFindingSchema;
|
|
249585
250066
|
exports.StorageMigrationFootageMoveInputSchema = StorageMigrationFootageMoveInputSchema;
|
|
249586
250067
|
exports.StorageMigrationInputSchema = StorageMigrationInputSchema;
|
|
249587
250068
|
exports.StorageMigrationJobSchema = StorageMigrationJobSchema;
|
|
249588
250069
|
exports.StorageMigrationLeaseInputSchema = StorageMigrationLeaseInputSchema;
|
|
249589
250070
|
exports.StorageMigrationMediaMoveInputSchema = StorageMigrationMediaMoveInputSchema;
|
|
250071
|
+
exports.StorageMigrationModeSchema = StorageMigrationModeSchema;
|
|
249590
250072
|
exports.StorageMigrationMoveSchema = StorageMigrationMoveSchema;
|
|
249591
250073
|
exports.StorageMigrationParticipantSchema = StorageMigrationParticipantSchema;
|
|
249592
250074
|
exports.StorageMigrationPhaseSchema = StorageMigrationPhaseSchema;
|
|
@@ -249662,6 +250144,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
249662
250144
|
exports.UNIT_TABLE = UNIT_TABLE;
|
|
249663
250145
|
exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
|
|
249664
250146
|
exports.UnitConversionError = UnitConversionError;
|
|
250147
|
+
exports.UnstampedEventMediaCountSchema = UnstampedEventMediaCountSchema;
|
|
249665
250148
|
exports.UpdateIntegrationInputSchema = UpdateIntegrationInputSchema;
|
|
249666
250149
|
exports.UpdateStatusSchema = UpdateStatusSchema;
|
|
249667
250150
|
exports.UpdateUserInputSchema = UpdateUserInputSchema;
|