@camstack/system 1.2.76 → 1.2.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.js +1 -1
  24. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  25. package/dist/builtins/loki-logging/index.js +1 -1
  26. package/dist/builtins/loki-logging/index.mjs +1 -1
  27. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  29. package/dist/builtins/platform-probe/index.js +1 -1
  30. package/dist/builtins/platform-probe/index.mjs +1 -1
  31. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  33. package/dist/builtins/snapshot/index.js +1 -1
  34. package/dist/builtins/snapshot/index.mjs +1 -1
  35. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  39. package/dist/builtins/storage-orchestrator/index.d.ts +2 -0
  40. package/dist/builtins/storage-orchestrator/index.js +1 -0
  41. package/dist/builtins/storage-orchestrator/index.mjs +2 -2
  42. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +73 -0
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +2 -0
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +468 -8
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +468 -9
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +7 -0
  47. package/dist/builtins/system-config/system-config.addon.js +1 -1
  48. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  49. package/dist/builtins/winston-logging/index.js +1 -1
  50. package/dist/builtins/winston-logging/index.mjs +1 -1
  51. package/dist/{dist-CX8Z8M6S.mjs → dist-CIIFhXqm.mjs} +416 -45
  52. package/dist/{dist-B1W4mT4z.js → dist-cINpngi1.js} +427 -44
  53. package/dist/http/reverse-proxy.d.ts +5 -0
  54. package/dist/index.js +5 -3
  55. package/dist/index.mjs +6 -4
  56. package/package.json +4 -1
@@ -1,4 +1,5 @@
1
- import { Pt as parseJsonObject, at as settingsStoreCapability, h as StorageLocationTypeSchema, mt as BaseAddon, st as storageCapability } from "../../dist-CX8Z8M6S.mjs";
1
+ import { It as parseJsonObject, ct as storageCapability, g as StorageMigrationJobSchema, gt as BaseAddon, h as StorageLocationTypeSchema, lt as storageMigrationCapability, ot as settingsStoreCapability } from "../../dist-CIIFhXqm.mjs";
2
+ import { randomUUID } from "node:crypto";
2
3
  import * as fs from "node:fs/promises";
3
4
  import * as path$1 from "node:path";
4
5
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -234,6 +235,52 @@ var StorageOrchestratorService = class {
234
235
  return null;
235
236
  }
236
237
  /**
238
+ * Repoint several logical defaults as one in-memory commit. The migration
239
+ * coordinator persists its `repointing` phase before calling this method, so
240
+ * a process crash during the backing-store writes is recovered by replaying
241
+ * the same idempotent target set before anyone resumes writers.
242
+ */
243
+ async setDefaultLocations(targetByType) {
244
+ if (targetByType.size === 0) return;
245
+ const now = Date.now();
246
+ const nextById = new Map(this.locations);
247
+ const touched = /* @__PURE__ */ new Map();
248
+ for (const [type, targetId] of targetByType) {
249
+ const target = nextById.get(targetId);
250
+ if (!target) throw new Error(`Storage location "${targetId}" not found`);
251
+ if (target.type !== type) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${type}"`);
252
+ for (const [id, location] of nextById) {
253
+ if (location.type !== type) continue;
254
+ const isDefault = id === targetId;
255
+ if (location.isDefault === isDefault) continue;
256
+ const updated = {
257
+ ...location,
258
+ isDefault,
259
+ updatedAt: now
260
+ };
261
+ nextById.set(id, updated);
262
+ touched.set(id, updated);
263
+ }
264
+ }
265
+ if (this.locationStore) {
266
+ const previous = /* @__PURE__ */ new Map();
267
+ for (const id of touched.keys()) {
268
+ const location = this.locations.get(id);
269
+ if (location) previous.set(id, location);
270
+ }
271
+ try {
272
+ for (const location of touched.values()) await this.locationStore.upsert(location);
273
+ } catch (err) {
274
+ for (const location of previous.values()) try {
275
+ await this.locationStore.upsert(location);
276
+ } catch {}
277
+ throw err;
278
+ }
279
+ }
280
+ this.locations.clear();
281
+ for (const [id, location] of nextById) this.locations.set(id, location);
282
+ }
283
+ /**
237
284
  * Insert or update a location. If `isDefault: true`, atomically
238
285
  * demotes any other default for the same type — operators always see
239
286
  * exactly one default per type.
@@ -928,6 +975,355 @@ var StoragePressureManager = class {
928
975
  }
929
976
  };
930
977
  //#endregion
978
+ //#region src/builtins/storage-orchestrator/storage-migration-coordinator.ts
979
+ var STORAGE_CLASSES = [
980
+ "recordings",
981
+ "recordingsLow",
982
+ "eventMedia"
983
+ ];
984
+ var PARTICIPANTS = [
985
+ "pipeline",
986
+ "recorder",
987
+ "analytics"
988
+ ];
989
+ var REVERSE_PARTICIPANTS = [
990
+ "analytics",
991
+ "recorder",
992
+ "pipeline"
993
+ ];
994
+ /**
995
+ * Durable orchestration layer over the independent recorder and event-media
996
+ * movers. It owns the only public migration state machine; participants expose
997
+ * intentionally narrow lease/move primitives and never alter camera settings.
998
+ */
999
+ var StorageMigrationCoordinator = class {
1000
+ deps;
1001
+ active = null;
1002
+ runPromise = null;
1003
+ startReserved = false;
1004
+ constructor(deps) {
1005
+ this.deps = deps;
1006
+ }
1007
+ async plan(input) {
1008
+ const moves = [];
1009
+ for (const storageClass of STORAGE_CLASSES) {
1010
+ const targetId = input.destinations[storageClass];
1011
+ if (targetId === void 0) continue;
1012
+ const source = this.deps.locations.getDefaultLocation(storageClass);
1013
+ if (!source) throw new Error(`No default storage location for "${storageClass}"`);
1014
+ const target = this.deps.locations.getLocationById(targetId);
1015
+ if (!target) throw new Error(`Storage location "${targetId}" not found`);
1016
+ if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
1017
+ if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
1018
+ moves.push({
1019
+ storageClass,
1020
+ fromLocationId: source.id,
1021
+ toLocationId: target.id,
1022
+ moverJobId: null,
1023
+ state: null,
1024
+ error: null
1025
+ });
1026
+ }
1027
+ if (moves.length === 0) throw new Error("select at least one storage class");
1028
+ return {
1029
+ destinations: input.destinations,
1030
+ moves: moves.map(({ storageClass, fromLocationId, toLocationId }) => ({
1031
+ storageClass,
1032
+ fromLocationId,
1033
+ toLocationId
1034
+ }))
1035
+ };
1036
+ }
1037
+ async start(input) {
1038
+ if (this.startReserved) throw new Error("storage migration is already active");
1039
+ this.startReserved = true;
1040
+ try {
1041
+ const existing = await this.status();
1042
+ if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
1043
+ await this.releaseAfterTerminal(existing);
1044
+ await this.persist(existing);
1045
+ if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
1046
+ }
1047
+ if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
1048
+ const plan = await this.plan(input);
1049
+ const now = this.deps.now();
1050
+ const job = {
1051
+ jobId: this.deps.newId(),
1052
+ phase: "planning",
1053
+ destinations: input.destinations,
1054
+ throttleMbps: input.throttleMbps ?? 40,
1055
+ moves: plan.moves.map((move) => ({
1056
+ ...move,
1057
+ moverJobId: null,
1058
+ state: null,
1059
+ error: null
1060
+ })),
1061
+ pauseLeaseId: null,
1062
+ pausedParticipants: [],
1063
+ repointed: false,
1064
+ cancelRequested: false,
1065
+ startedAt: now,
1066
+ updatedAt: now,
1067
+ finishedAt: null,
1068
+ error: null
1069
+ };
1070
+ this.active = job;
1071
+ await this.persist(job);
1072
+ this.runPromise = this.run(job);
1073
+ this.runPromise;
1074
+ return job.jobId;
1075
+ } finally {
1076
+ this.startReserved = false;
1077
+ }
1078
+ }
1079
+ async status(jobId) {
1080
+ const job = this.active ?? await this.deps.state.get();
1081
+ if (jobId !== void 0 && job?.jobId !== jobId) return null;
1082
+ return job;
1083
+ }
1084
+ async cancel(jobId) {
1085
+ const job = await this.status(jobId);
1086
+ if (!job || isTerminal(job) || job.repointed) return false;
1087
+ job.cancelRequested = true;
1088
+ await this.persist(job);
1089
+ await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
1090
+ return true;
1091
+ }
1092
+ /** Boot recovery resumes a durable unfinished state. A missing in-memory
1093
+ * child mover is recreated from the same copy-if-absent input. */
1094
+ async recover() {
1095
+ const job = await this.deps.state.get();
1096
+ if (!job) return;
1097
+ if (job.cancelRequested) {
1098
+ await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
1099
+ await this.waitForMoves(job);
1100
+ await this.releaseAfterTerminal(job);
1101
+ job.phase = "cancelled";
1102
+ job.finishedAt ??= this.deps.now();
1103
+ await this.persist(job);
1104
+ return;
1105
+ }
1106
+ if (isTerminal(job)) {
1107
+ if (job.pausedParticipants.length > 0) {
1108
+ await this.releaseAfterTerminal(job);
1109
+ await this.persist(job);
1110
+ }
1111
+ return;
1112
+ }
1113
+ this.active = job;
1114
+ job.pauseLeaseId ??= this.deps.newId();
1115
+ if (job.repointed && job.phase === "resuming") job.phase = "refreshing";
1116
+ await this.persist(job);
1117
+ try {
1118
+ await this.pauseParticipants(job, true);
1119
+ } catch (err) {
1120
+ job.error = err instanceof Error ? err.message : String(err);
1121
+ await this.persist(job);
1122
+ return;
1123
+ }
1124
+ if (this.runPromise === null) {
1125
+ this.runPromise = this.run(job);
1126
+ this.runPromise;
1127
+ }
1128
+ }
1129
+ async run(job) {
1130
+ try {
1131
+ if (job.phase === "planning") await this.setPhase(job, "pausing");
1132
+ if (job.phase === "pausing") {
1133
+ job.pauseLeaseId ??= job.jobId;
1134
+ await this.pauseParticipants(job);
1135
+ if (job.cancelRequested) return this.finishCancelled(job);
1136
+ await this.setPhase(job, "moving");
1137
+ }
1138
+ if (job.phase === "moving") {
1139
+ await this.startOrResumeMoves(job);
1140
+ await this.waitForMoves(job);
1141
+ if (job.cancelRequested) return this.finishCancelled(job);
1142
+ await this.setPhase(job, "verifying");
1143
+ }
1144
+ if (job.phase === "verifying") {
1145
+ await this.verifyMoves(job);
1146
+ if (job.cancelRequested) return this.finishCancelled(job);
1147
+ await this.setPhase(job, "repointing");
1148
+ }
1149
+ if (job.phase === "repointing") {
1150
+ const targets = new Map(job.moves.map((move) => [move.storageClass, move.toLocationId]));
1151
+ await this.deps.locations.setDefaultLocations(targets);
1152
+ job.repointed = true;
1153
+ await this.setPhase(job, "refreshing");
1154
+ }
1155
+ if (job.phase === "refreshing") {
1156
+ const leaseId = requireLease(job);
1157
+ if (job.moves.some((move) => move.storageClass !== "eventMedia")) await this.deps.participants.recorder.refresh(leaseId);
1158
+ if (job.moves.some((move) => move.storageClass === "eventMedia")) await this.deps.participants.analytics.refresh(leaseId);
1159
+ await this.setPhase(job, "resuming");
1160
+ }
1161
+ if (job.phase === "resuming") {
1162
+ await this.resumeParticipants(job);
1163
+ await this.setPhase(job, "done");
1164
+ }
1165
+ } catch (err) {
1166
+ job.error = err instanceof Error ? err.message : String(err);
1167
+ if (job.repointed) {
1168
+ job.phase = "refreshing";
1169
+ job.finishedAt = null;
1170
+ await this.persist(job);
1171
+ return;
1172
+ }
1173
+ job.phase = "failed";
1174
+ job.finishedAt = this.deps.now();
1175
+ await this.releaseAfterTerminal(job);
1176
+ await this.persist(job);
1177
+ } finally {
1178
+ if (isTerminal(job)) {
1179
+ await this.releaseAfterTerminal(job);
1180
+ this.active = job;
1181
+ }
1182
+ this.runPromise = null;
1183
+ }
1184
+ }
1185
+ async pauseParticipants(job, reacquire = false) {
1186
+ const leaseId = requireLease(job);
1187
+ for (const name of PARTICIPANTS) {
1188
+ if (job.cancelRequested) return;
1189
+ if (!reacquire && job.pausedParticipants.includes(name)) continue;
1190
+ await this.participant(name).pause(leaseId);
1191
+ if (!job.pausedParticipants.includes(name)) job.pausedParticipants.push(name);
1192
+ await this.persist(job);
1193
+ }
1194
+ }
1195
+ async resumeParticipants(job) {
1196
+ const leaseId = requireLease(job);
1197
+ for (const name of REVERSE_PARTICIPANTS) {
1198
+ if (!job.pausedParticipants.includes(name)) continue;
1199
+ await this.participant(name).resume(leaseId);
1200
+ job.pausedParticipants = job.pausedParticipants.filter((participant) => participant !== name);
1201
+ await this.persist(job);
1202
+ }
1203
+ }
1204
+ async releaseAfterTerminal(job) {
1205
+ if (job.pauseLeaseId === null) return;
1206
+ for (const name of REVERSE_PARTICIPANTS) {
1207
+ if (!job.pausedParticipants.includes(name)) continue;
1208
+ try {
1209
+ await this.participant(name).resume(job.pauseLeaseId);
1210
+ job.pausedParticipants = job.pausedParticipants.filter((participant) => participant !== name);
1211
+ await this.persist(job);
1212
+ } catch {}
1213
+ }
1214
+ }
1215
+ async startOrResumeMoves(job) {
1216
+ if (job.cancelRequested) return;
1217
+ const mediaMove = job.moves.find((move) => move.storageClass === "eventMedia");
1218
+ if (mediaMove?.moverJobId === null) {
1219
+ mediaMove.moverJobId = await this.startMove(job, mediaMove);
1220
+ await this.persist(job);
1221
+ }
1222
+ const footageMoves = job.moves.filter((move) => move.storageClass !== "eventMedia");
1223
+ for (let index = 0; index < footageMoves.length; index++) {
1224
+ const move = footageMoves[index];
1225
+ if (move.moverJobId !== null) continue;
1226
+ if (!footageMoves.slice(0, index).every((predecessor) => predecessor.state === "done")) return;
1227
+ move.moverJobId = await this.startMove(job, move);
1228
+ await this.persist(job);
1229
+ return;
1230
+ }
1231
+ }
1232
+ async waitForMoves(job) {
1233
+ const sleep = this.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1234
+ const pollMs = this.deps.pollMs ?? 250;
1235
+ for (;;) {
1236
+ if (!job.cancelRequested) await this.startOrResumeMoves(job);
1237
+ let complete = true;
1238
+ for (const [index, move] of job.moves.entries()) {
1239
+ if (job.cancelRequested && move.moverJobId === null) continue;
1240
+ if (move.storageClass !== "eventMedia" && job.moves.slice(0, index).filter((predecessor) => predecessor.storageClass !== "eventMedia").some((predecessor) => predecessor.state !== "done")) {
1241
+ complete = false;
1242
+ continue;
1243
+ }
1244
+ if (move.moverJobId === null) {
1245
+ if (!job.cancelRequested) complete = false;
1246
+ continue;
1247
+ }
1248
+ const status = await this.moveStatus(move);
1249
+ if (status === null) {
1250
+ if (job.cancelRequested) {
1251
+ move.state = "cancelled";
1252
+ continue;
1253
+ }
1254
+ move.moverJobId = await this.startMove(job, move);
1255
+ move.state = "running";
1256
+ await this.persist(job);
1257
+ complete = false;
1258
+ continue;
1259
+ }
1260
+ move.state = status.state;
1261
+ move.error = status.error;
1262
+ if (status.state === "failed") throw new Error(move.error ?? `${move.storageClass} move failed`);
1263
+ if (status.state === "cancelled") {
1264
+ job.cancelRequested = true;
1265
+ continue;
1266
+ }
1267
+ if (status.state !== "done") complete = false;
1268
+ }
1269
+ await this.persist(job);
1270
+ if (complete) return;
1271
+ await sleep(pollMs);
1272
+ }
1273
+ }
1274
+ async verifyMoves(job) {
1275
+ for (const move of job.moves) if (move.state !== "done") throw new Error(`${move.storageClass} move did not complete verification`);
1276
+ }
1277
+ async startMove(job, move) {
1278
+ if (move.storageClass === "eventMedia") return (await this.deps.participants.analytics.startMove({
1279
+ toLocationId: move.toLocationId,
1280
+ throttleMbps: job.throttleMbps,
1281
+ leaseId: requireLease(job)
1282
+ })).jobId;
1283
+ return (await this.deps.participants.recorder.startMove({
1284
+ fromLocationId: move.fromLocationId,
1285
+ toLocationId: move.toLocationId,
1286
+ footageClass: move.storageClass,
1287
+ throttleMbps: job.throttleMbps,
1288
+ leaseId: requireLease(job)
1289
+ })).jobId;
1290
+ }
1291
+ async moveStatus(move) {
1292
+ if (move.moverJobId === null) return null;
1293
+ return move.storageClass === "eventMedia" ? this.deps.participants.analytics.getMove(move.moverJobId) : this.deps.participants.recorder.getMove(move.moverJobId);
1294
+ }
1295
+ async cancelMove(move) {
1296
+ if (move.moverJobId === null) return;
1297
+ if (move.storageClass === "eventMedia") await this.deps.participants.analytics.cancelMove(move.moverJobId);
1298
+ else await this.deps.participants.recorder.cancelMove(move.moverJobId);
1299
+ }
1300
+ participant(name) {
1301
+ return this.deps.participants[name];
1302
+ }
1303
+ async setPhase(job, phase) {
1304
+ job.phase = phase;
1305
+ if (isTerminal(job)) job.finishedAt = this.deps.now();
1306
+ await this.persist(job);
1307
+ }
1308
+ async finishCancelled(job) {
1309
+ job.phase = "cancelled";
1310
+ job.finishedAt = this.deps.now();
1311
+ await this.releaseAfterTerminal(job);
1312
+ await this.persist(job);
1313
+ }
1314
+ async persist(job) {
1315
+ job.updatedAt = this.deps.now();
1316
+ await this.deps.state.set(job);
1317
+ }
1318
+ };
1319
+ function requireLease(job) {
1320
+ if (job.pauseLeaseId === null) throw new Error("storage migration has no maintenance lease");
1321
+ return job.pauseLeaseId;
1322
+ }
1323
+ function isTerminal(job) {
1324
+ return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
1325
+ }
1326
+ //#endregion
931
1327
  //#region src/builtins/storage-orchestrator/storage-orchestrator.addon.ts
932
1328
  /**
933
1329
  * `storage-orchestrator` builtin — singleton owner of the consumer-
@@ -973,6 +1369,7 @@ var StorageOrchestratorAddon = class extends BaseAddon {
973
1369
  pressureManager = null;
974
1370
  pressureTimer = null;
975
1371
  pressureSweepInFlight = false;
1372
+ migration = null;
976
1373
  /**
977
1374
  * Cached `providerId → nodeLocal` snapshot (SP1). Backs the orchestrator's
978
1375
  * synchronous `NodeLocalResolver` — `getProviderInfo()` is async, so we
@@ -1166,6 +1563,57 @@ var StorageOrchestratorAddon = class extends BaseAddon {
1166
1563
  }
1167
1564
  }
1168
1565
  };
1566
+ const migration = new StorageMigrationCoordinator({
1567
+ locations: service,
1568
+ state: this.state("storage-migration", StorageMigrationJobSchema.nullable(), null),
1569
+ participants: {
1570
+ pipeline: {
1571
+ pause: async (leaseId) => {
1572
+ await this.ctx.api.pipelineOrchestrator.pauseForStorageMigration.mutate({ leaseId });
1573
+ },
1574
+ resume: async (leaseId) => {
1575
+ await this.ctx.api.pipelineOrchestrator.resumeForStorageMigration.mutate({ leaseId });
1576
+ }
1577
+ },
1578
+ recorder: {
1579
+ pause: async (leaseId) => {
1580
+ await this.ctx.api.recording.pauseForStorageMigration.mutate({ leaseId });
1581
+ },
1582
+ resume: async (leaseId) => {
1583
+ await this.ctx.api.recording.resumeForStorageMigration.mutate({ leaseId });
1584
+ },
1585
+ startMove: (input) => this.ctx.api.recording.startStorageMigrationMove.mutate(input),
1586
+ getMove: (jobId) => this.ctx.api.recording.getStorageMigrationMoveStatus.query({ jobId }),
1587
+ cancelMove: async (jobId) => (await this.ctx.api.recording.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
1588
+ refresh: async (leaseId) => {
1589
+ await this.ctx.api.recording.refreshStorageLocationsForMigration.mutate({ leaseId });
1590
+ }
1591
+ },
1592
+ analytics: {
1593
+ pause: async (leaseId) => {
1594
+ await this.ctx.api.pipelineAnalytics.pauseForStorageMigration.mutate({ leaseId });
1595
+ },
1596
+ resume: async (leaseId) => {
1597
+ await this.ctx.api.pipelineAnalytics.resumeForStorageMigration.mutate({ leaseId });
1598
+ },
1599
+ startMove: (input) => this.ctx.api.pipelineAnalytics.startStorageMigrationMove.mutate(input),
1600
+ getMove: (jobId) => this.ctx.api.pipelineAnalytics.getStorageMigrationMoveStatus.query({ jobId }),
1601
+ cancelMove: async (jobId) => (await this.ctx.api.pipelineAnalytics.cancelStorageMigrationMove.mutate({ jobId })).cancelled,
1602
+ refresh: async (leaseId) => {
1603
+ await this.ctx.api.pipelineAnalytics.refreshStorageLocationsForMigration.mutate({ leaseId });
1604
+ }
1605
+ }
1606
+ },
1607
+ now: () => Date.now(),
1608
+ newId: () => randomUUID()
1609
+ });
1610
+ this.migration = migration;
1611
+ const migrationProvider = {
1612
+ plan: (input) => migration.plan(input),
1613
+ start: async (input) => ({ jobId: await migration.start(input) }),
1614
+ status: ({ jobId }) => migration.status(jobId),
1615
+ cancel: async ({ jobId }) => ({ cancelled: await migration.cancel(jobId) })
1616
+ };
1169
1617
  await this.seedFromDeclarations();
1170
1618
  const eventBus = this.ctx.eventBus;
1171
1619
  if (eventBus) {
@@ -1190,13 +1638,20 @@ var StorageOrchestratorAddon = class extends BaseAddon {
1190
1638
  this.runPressureSweep();
1191
1639
  }, PRESSURE_SWEEP_INTERVAL_MS);
1192
1640
  this.ctx.logger.info("Storage orchestrator initialized");
1193
- return [{
1194
- capability: storageCapability,
1195
- provider
1196
- }, {
1197
- capability: settingsStoreCapability,
1198
- provider: createDataStoreDispatch(getEngines)
1199
- }];
1641
+ return [
1642
+ {
1643
+ capability: storageCapability,
1644
+ provider
1645
+ },
1646
+ {
1647
+ capability: storageMigrationCapability,
1648
+ provider: migrationProvider
1649
+ },
1650
+ {
1651
+ capability: settingsStoreCapability,
1652
+ provider: createDataStoreDispatch(getEngines)
1653
+ }
1654
+ ];
1200
1655
  }
1201
1656
  async onShutdown() {
1202
1657
  for (const dispose of this.seedSubscriptionDisposers) try {
@@ -1210,6 +1665,10 @@ var StorageOrchestratorAddon = class extends BaseAddon {
1210
1665
  this.pressureManager = null;
1211
1666
  this.service?.clearSessions();
1212
1667
  this.service = null;
1668
+ this.migration = null;
1669
+ }
1670
+ async onHubReachable() {
1671
+ await this.migration?.recover();
1213
1672
  }
1214
1673
  /**
1215
1674
  * Resolve the live engine from the capability registry and wrap it in a
@@ -1431,4 +1890,4 @@ var StorageOrchestratorAddon = class extends BaseAddon {
1431
1890
  }
1432
1891
  };
1433
1892
  //#endregion
1434
- export { StorageOrchestratorAddon, StorageOrchestratorAddon as default, StorageOrchestratorService as n, SqliteLocationStore as t };
1893
+ export { StorageOrchestratorAddon, StorageOrchestratorAddon as default, SqliteLocationStore as n, StorageOrchestratorService as r, StorageMigrationCoordinator as t };
@@ -193,6 +193,13 @@ export declare class StorageOrchestratorService {
193
193
  backfillNodeIds(hubNodeId: string): Promise<void>;
194
194
  listLocations(filter?: ListLocationsFilter): readonly StorageLocation[];
195
195
  getDefaultLocation(type: string): StorageLocation | null;
196
+ /**
197
+ * Repoint several logical defaults as one in-memory commit. The migration
198
+ * coordinator persists its `repointing` phase before calling this method, so
199
+ * a process crash during the backing-store writes is recovered by replaying
200
+ * the same idempotent target set before anyone resumes writers.
201
+ */
202
+ setDefaultLocations(targetByType: ReadonlyMap<string, string>): Promise<void>;
196
203
  /**
197
204
  * Insert or update a location. If `isDefault: true`, atomically
198
205
  * demotes any other default for the same type — operators always see
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B1W4mT4z.js");
6
+ const require_dist = require("../../dist-cINpngi1.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { jt as hydrateSchema, mt as BaseAddon, pt as errMsg } from "../../dist-CX8Z8M6S.mjs";
1
+ import { Nt as hydrateSchema, gt as BaseAddon, ht as errMsg } from "../../dist-CIIFhXqm.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B1W4mT4z.js");
6
+ const require_dist = require("../../dist-cINpngi1.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { mt as BaseAddon, q as logDestinationCapability } from "../../dist-CX8Z8M6S.mjs";
1
+ import { J as logDestinationCapability, gt as BaseAddon } from "../../dist-CIIFhXqm.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";