@camstack/agent 1.1.56 → 1.1.58

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.
@@ -65,31 +65,41 @@ var require_dist = __commonJS({
65
65
  var src_exports = {};
66
66
  __export(src_exports, {
67
67
  AGENT_ROOT_SPEC: () => AGENT_ROOT_SPEC,
68
+ CURRENT_DIRNAME: () => CURRENT_DIRNAME,
68
69
  DEV_UPLOADS_DIRNAME: () => DEV_UPLOADS_DIRNAME,
69
70
  DEV_UPLOADS_KEEP_COUNT: () => DEV_UPLOADS_KEEP_COUNT,
70
71
  DEV_UPLOAD_MANIFEST_FILE: () => DEV_UPLOAD_MANIFEST_FILE,
71
72
  HOST_EXTERNAL_SPECIFIERS: () => HOST_EXTERNAL_SPECIFIERS,
72
73
  HUB_ROOT_SPEC: () => HUB_ROOT_SPEC,
74
+ PENDING_ROOT_SWAP_FILE: () => PENDING_ROOT_SWAP_FILE,
75
+ REQUIRED_NATIVE_PACKAGES: () => REQUIRED_NATIVE_PACKAGES,
73
76
  RESTART_INTENT_FILE: () => RESTART_INTENT_FILE,
74
77
  RootUpdateService: () => RootUpdateService,
75
78
  SERVER_ROOT_DIRNAME: () => SERVER_ROOT_DIRNAME,
76
79
  SERVER_ROOT_STATE_FILE: () => SERVER_ROOT_STATE_FILE,
80
+ applyPendingRootSwap: () => applyPendingRootSwap,
81
+ clearPendingRootSwap: () => clearPendingRootSwap,
77
82
  clearRestartIntentMarker: () => clearRestartIntentMarker,
78
83
  compareSemver: () => compareSemver,
84
+ currentDir: () => currentDir,
85
+ currentEntryPath: () => currentEntryPath,
79
86
  detectWorkspaceRoot: () => detectWorkspaceRoot,
80
87
  devChannelEpoch: () => devChannelEpoch,
81
88
  devUploadManifestPath: () => devUploadManifestPath,
82
89
  devUploadVersionDir: () => devUploadVersionDir,
83
90
  devUploadsDir: () => devUploadsDir,
84
91
  emptyServerRootState: () => emptyServerRootState,
92
+ findMissingNativePrebuilds: () => findMissingNativePrebuilds,
85
93
  isDevChannelVersion: () => isDevChannelVersion,
86
94
  isHostExternal: () => isHostExternal,
87
95
  isServerRootState: () => isServerRootState,
96
+ migrateToSingleCopy: () => migrateToSingleCopy,
88
97
  minNodeMajorOf: () => minNodeMajorOf,
98
+ pendingRootSwapPath: () => pendingRootSwapPath,
89
99
  planBoot: () => planBoot,
90
100
  readDevUploadManifest: () => readDevUploadManifest,
101
+ readPendingRootSwap: () => readPendingRootSwap,
91
102
  readRestartIntentMarker: () => readRestartIntentMarker,
92
- readSeedVersion: () => readSeedVersion,
93
103
  readServerRootState: () => readServerRootState,
94
104
  registerActiveRootResolver: () => registerActiveRootResolver,
95
105
  restartIntentMarkerPath: () => restartIntentMarkerPath,
@@ -97,11 +107,15 @@ var require_dist = __commonJS({
97
107
  rootPackageDir: () => rootPackageDir,
98
108
  runNodeStarter: () => runNodeStarter,
99
109
  serverRootDir: () => serverRootDir,
110
+ stagingDirPath: () => stagingDirPath,
100
111
  stateFilePath: () => stateFilePath,
112
+ sweepTransientRootDirs: () => sweepTransientRootDirs,
113
+ validateClosureDir: () => validateClosureDir,
101
114
  validateVersionDir: () => validateVersionDir,
102
115
  versionDir: () => versionDir,
103
116
  versionsDir: () => versionsDir,
104
117
  writeDevUploadManifest: () => writeDevUploadManifest,
118
+ writePendingRootSwap: () => writePendingRootSwap,
105
119
  writeRestartIntentMarker: () => writeRestartIntentMarker,
106
120
  writeServerRootState: () => writeServerRootState
107
121
  });
@@ -266,13 +280,12 @@ var require_dist = __commonJS({
266
280
  const major = Number.parseInt(match[1] ?? "", 10);
267
281
  return Number.isNaN(major) ? null : major;
268
282
  }
269
- function validateVersionDir(rootDir, version, nodeMajor, spec) {
270
- const vDir = versionDir(rootDir, version);
271
- const entry = rootEntryPath(vDir, spec);
283
+ function validateClosureDir(closureDir, nodeMajor, spec, expectedVersion) {
284
+ const entry = rootEntryPath(closureDir, spec);
272
285
  if (!fs2.existsSync(entry)) {
273
286
  return `root entry missing: ${entry}`;
274
287
  }
275
- const pkgJsonPath = path2.join(rootPackageDir(vDir, spec), "package.json");
288
+ const pkgJsonPath = path2.join(rootPackageDir(closureDir, spec), "package.json");
276
289
  let pkg;
277
290
  try {
278
291
  const raw = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
@@ -286,8 +299,8 @@ var require_dist = __commonJS({
286
299
  if (pkg["name"] !== spec.packageName) {
287
300
  return `package name mismatch: expected ${spec.packageName}, got ${String(pkg["name"])}`;
288
301
  }
289
- if (pkg["version"] !== version) {
290
- return `package version mismatch: dir says ${version}, package.json says ${String(pkg["version"])}`;
302
+ if (expectedVersion !== void 0 && pkg["version"] !== expectedVersion) {
303
+ return `package version mismatch: expected ${expectedVersion}, package.json says ${String(pkg["version"])}`;
291
304
  }
292
305
  const engines = pkg["engines"];
293
306
  if (typeof engines === "object" && engines !== null) {
@@ -301,6 +314,225 @@ var require_dist = __commonJS({
301
314
  }
302
315
  return null;
303
316
  }
317
+ function validateVersionDir(rootDir, version, nodeMajor, spec) {
318
+ return validateClosureDir(versionDir(rootDir, version), nodeMajor, spec, version);
319
+ }
320
+ var fs3 = __toESM2(__require("fs"));
321
+ var path3 = __toESM2(__require("path"));
322
+ var CURRENT_DIRNAME = "current";
323
+ var PENDING_ROOT_SWAP_FILE = ".pending-root-swap.json";
324
+ var LEGACY_FRAMEWORK_MARKERS = [
325
+ ".pending-framework-swap.json",
326
+ ".framework-swap-confirm.json"
327
+ ];
328
+ function currentDir(rootDir) {
329
+ return path3.join(rootDir, CURRENT_DIRNAME);
330
+ }
331
+ function currentEntryPath(rootDir, spec) {
332
+ return rootEntryPath(currentDir(rootDir), spec);
333
+ }
334
+ function stagingDirPath(rootDir, target, pid, now) {
335
+ return path3.join(rootDir, `.staging-${target}-${pid}-${now}`);
336
+ }
337
+ function pendingRootSwapPath(rootDir) {
338
+ return path3.join(rootDir, PENDING_ROOT_SWAP_FILE);
339
+ }
340
+ function isPendingRootSwap(v) {
341
+ if (typeof v !== "object" || v === null) return false;
342
+ const m = v;
343
+ return m["schemaVersion"] === 1 && typeof m["version"] === "string" && typeof m["stagingPath"] === "string" && typeof m["requestedAtMs"] === "number";
344
+ }
345
+ function readPendingRootSwap(rootDir) {
346
+ try {
347
+ const raw = JSON.parse(fs3.readFileSync(pendingRootSwapPath(rootDir), "utf-8"));
348
+ return isPendingRootSwap(raw) ? raw : null;
349
+ } catch {
350
+ return null;
351
+ }
352
+ }
353
+ function writePendingRootSwap(rootDir, marker) {
354
+ fs3.mkdirSync(rootDir, { recursive: true });
355
+ const target = pendingRootSwapPath(rootDir);
356
+ const tmp = `${target}.tmp`;
357
+ fs3.writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
358
+ fs3.renameSync(tmp, target);
359
+ }
360
+ function clearPendingRootSwap(rootDir) {
361
+ try {
362
+ fs3.rmSync(pendingRootSwapPath(rootDir), { force: true });
363
+ } catch {
364
+ }
365
+ }
366
+ var REQUIRED_NATIVE_PACKAGES = ["better-sqlite3", "sharp", "node-av"];
367
+ var NATIVE_SEARCH_DIRS = {
368
+ "better-sqlite3": ["better-sqlite3"],
369
+ sharp: ["sharp", "@img"],
370
+ "node-av": ["node-av", "@seydx"]
371
+ };
372
+ function hasDotNode(dir, maxDepth) {
373
+ let entries;
374
+ try {
375
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
376
+ } catch {
377
+ return false;
378
+ }
379
+ for (const entry of entries) {
380
+ if (entry.isFile() && entry.name.endsWith(".node")) return true;
381
+ }
382
+ if (maxDepth <= 0) return false;
383
+ for (const entry of entries) {
384
+ if (entry.isDirectory() && hasDotNode(path3.join(dir, entry.name), maxDepth - 1)) return true;
385
+ }
386
+ return false;
387
+ }
388
+ function findMissingNativePrebuilds(closureDir) {
389
+ const nm = path3.join(closureDir, "node_modules");
390
+ const missing = [];
391
+ for (const pkg of REQUIRED_NATIVE_PACKAGES) {
392
+ const searchDirs = NATIVE_SEARCH_DIRS[pkg] ?? [pkg];
393
+ const found = searchDirs.some((rel) => hasDotNode(path3.join(nm, ...rel.split("/")), 4));
394
+ if (!found) missing.push(pkg);
395
+ }
396
+ return missing;
397
+ }
398
+ function rmrf(target) {
399
+ try {
400
+ fs3.rmSync(target, { recursive: true, force: true });
401
+ } catch {
402
+ }
403
+ }
404
+ function errMsg(err) {
405
+ return err instanceof Error ? err.message : String(err);
406
+ }
407
+ function applyPendingRootSwap(rootDir, spec, nodeMajor, now, log) {
408
+ const marker = readPendingRootSwap(rootDir);
409
+ if (marker === null) return { applied: false, version: null, reason: "no pending swap" };
410
+ const staging = marker.stagingPath;
411
+ const dest = currentDir(rootDir);
412
+ const invalid = validateClosureDir(staging, nodeMajor, spec, marker.version);
413
+ if (invalid !== null) {
414
+ if (!fs3.existsSync(staging) && validateClosureDir(dest, nodeMajor, spec, marker.version) === null) {
415
+ clearPendingRootSwap(rootDir);
416
+ log(`[single-copy] pending swap ${marker.version} already applied \u2014 cleared marker`);
417
+ return { applied: true, version: marker.version, reason: "already applied" };
418
+ }
419
+ log(`[single-copy] staged swap ${marker.version} invalid \u2014 discarding (${invalid})`);
420
+ rmrf(staging);
421
+ clearPendingRootSwap(rootDir);
422
+ return { applied: false, version: null, reason: invalid };
423
+ }
424
+ const missing = findMissingNativePrebuilds(staging);
425
+ if (missing.length > 0) {
426
+ log(
427
+ `[single-copy] staged swap ${marker.version} missing native prebuilds: ${missing.join(", ")} \u2014 discarding`
428
+ );
429
+ rmrf(staging);
430
+ clearPendingRootSwap(rootDir);
431
+ return {
432
+ applied: false,
433
+ version: null,
434
+ reason: `missing native prebuilds: ${missing.join(", ")}`
435
+ };
436
+ }
437
+ let trash = null;
438
+ try {
439
+ if (fs3.existsSync(dest)) {
440
+ trash = path3.join(rootDir, `.trash-${now()}-${process.pid}`);
441
+ fs3.renameSync(dest, trash);
442
+ }
443
+ try {
444
+ fs3.renameSync(staging, dest);
445
+ } catch (err) {
446
+ if (trash !== null && !fs3.existsSync(dest)) {
447
+ try {
448
+ fs3.renameSync(trash, dest);
449
+ } catch {
450
+ }
451
+ }
452
+ throw err;
453
+ }
454
+ } catch (err) {
455
+ log(`[single-copy] swap of ${marker.version} FAILED \u2014 current preserved (${errMsg(err)})`);
456
+ return { applied: false, version: null, reason: errMsg(err) };
457
+ }
458
+ if (trash !== null) rmrf(trash);
459
+ clearPendingRootSwap(rootDir);
460
+ log(`[single-copy] applied staged root swap \u2192 ${marker.version}`);
461
+ return { applied: true, version: marker.version, reason: "applied" };
462
+ }
463
+ function migrateToSingleCopy(rootDir, spec, nodeMajor, now, log) {
464
+ const dest = currentDir(rootDir);
465
+ if (fs3.existsSync(dest) && validateClosureDir(dest, nodeMajor, spec) === null) {
466
+ return { migrated: false, version: null, reason: "current already present" };
467
+ }
468
+ const candidates = [];
469
+ const legacyState = readServerRootState(rootDir);
470
+ if (legacyState !== null) {
471
+ for (const v of [
472
+ legacyState.currentVersion,
473
+ legacyState.pendingBoot?.version ?? null,
474
+ legacyState.previousVersion
475
+ ]) {
476
+ if (typeof v === "string" && !candidates.includes(v)) candidates.push(v);
477
+ }
478
+ }
479
+ try {
480
+ for (const entry of fs3.readdirSync(versionsDir(rootDir))) {
481
+ if (!entry.startsWith(".") && !candidates.includes(entry)) candidates.push(entry);
482
+ }
483
+ } catch {
484
+ }
485
+ const source = candidates.find(
486
+ (v) => validateClosureDir(versionDir(rootDir, v), nodeMajor, spec, v) === null
487
+ );
488
+ if (source === void 0) {
489
+ return { migrated: false, version: null, reason: "no valid legacy versions/<X> to adopt" };
490
+ }
491
+ if (fs3.existsSync(dest)) {
492
+ const aside = path3.join(rootDir, `.trash-${now()}-${process.pid}-stale-current`);
493
+ try {
494
+ fs3.renameSync(dest, aside);
495
+ } catch {
496
+ rmrf(dest);
497
+ }
498
+ }
499
+ try {
500
+ fs3.renameSync(versionDir(rootDir, source), dest);
501
+ } catch (err) {
502
+ log(`[single-copy] migration rename of versions/${source} failed (${errMsg(err)})`);
503
+ return { migrated: false, version: null, reason: errMsg(err) };
504
+ }
505
+ rmrf(versionsDir(rootDir));
506
+ rmrf(stateFilePath(rootDir));
507
+ const dataDir = path3.dirname(rootDir);
508
+ for (const marker of LEGACY_FRAMEWORK_MARKERS) rmrf(path3.join(dataDir, marker));
509
+ log(`[single-copy] migrated legacy versions/${source} \u2192 current`);
510
+ return { migrated: true, version: source, reason: "adopted legacy version" };
511
+ }
512
+ function sweepTransientRootDirs(rootDir, now, staleMs) {
513
+ let entries;
514
+ try {
515
+ entries = fs3.readdirSync(rootDir);
516
+ } catch {
517
+ return;
518
+ }
519
+ for (const entry of entries) {
520
+ if (!entry.startsWith(".staging-") && !entry.startsWith(".trash-")) continue;
521
+ const full = path3.join(rootDir, entry);
522
+ try {
523
+ if (now - fs3.statSync(full).mtimeMs < staleMs) continue;
524
+ } catch {
525
+ continue;
526
+ }
527
+ rmrf(full);
528
+ }
529
+ }
530
+ function planBoot(currentValid) {
531
+ if (currentValid) {
532
+ return { kind: "current", reason: "single-copy closure present and valid" };
533
+ }
534
+ return { kind: "baked", reason: "no valid single-copy closure \u2014 booting the baked seed" };
535
+ }
304
536
  function parse(version) {
305
537
  const dashIdx = version.indexOf("-");
306
538
  const base = dashIdx === -1 ? version : version.slice(0, dashIdx);
@@ -328,133 +560,26 @@ var require_dist = __commonJS({
328
560
  if (pa.prerelease > pb.prerelease) return 1;
329
561
  return 0;
330
562
  }
331
- function planBoot(state, isValidVersion, now, seedVersion = null) {
332
- if (state === null) {
333
- return { kind: "baked", reason: "no server-root state", stateToWrite: null };
334
- }
335
- let next = state;
336
- let changed = false;
337
- if (next.pendingBoot !== null) {
338
- const pending = next.pendingBoot;
339
- if (pending.bootAttempts >= 1) {
340
- next = {
341
- ...next,
342
- pendingBoot: null,
343
- rolledBack: {
344
- fromVersion: pending.version,
345
- toVersion: next.currentVersion,
346
- atMs: now(),
347
- reason: "probation boot did not reach ready \u2014 rolled back"
348
- }
349
- };
350
- changed = true;
351
- } else if (!isValidVersion(pending.version)) {
352
- next = {
353
- ...next,
354
- pendingBoot: null,
355
- rolledBack: {
356
- fromVersion: pending.version,
357
- toVersion: next.currentVersion,
358
- atMs: now(),
359
- reason: "staged version failed validation"
360
- }
361
- };
362
- changed = true;
363
- } else {
364
- return {
365
- kind: "data-root",
366
- version: pending.version,
367
- probation: true,
368
- stateToWrite: {
369
- ...next,
370
- pendingBoot: { ...pending, bootAttempts: pending.bootAttempts + 1 }
371
- }
372
- };
373
- }
374
- }
375
- if (next.currentVersion !== null) {
376
- if (isValidVersion(next.currentVersion)) {
377
- if (!changed && seedVersion !== null && compareSemver(seedVersion, next.currentVersion) > 0) {
378
- return {
379
- kind: "baked",
380
- reason: `baked seed ${seedVersion} is newer than active data-root ${next.currentVersion} \u2014 adopting the image seed`,
381
- stateToWrite: {
382
- ...next,
383
- currentVersion: null,
384
- previousVersion: null,
385
- pendingBoot: null,
386
- rolledBack: null
387
- }
388
- };
389
- }
390
- return {
391
- kind: "data-root",
392
- version: next.currentVersion,
393
- probation: false,
394
- stateToWrite: changed ? next : null
395
- };
396
- }
397
- const broken = next.currentVersion;
398
- if (next.previousVersion !== null && isValidVersion(next.previousVersion)) {
399
- const fallback = next.previousVersion;
400
- return {
401
- kind: "data-root",
402
- version: fallback,
403
- probation: false,
404
- stateToWrite: {
405
- ...next,
406
- currentVersion: fallback,
407
- previousVersion: null,
408
- rolledBack: {
409
- fromVersion: broken,
410
- toVersion: fallback,
411
- atMs: now(),
412
- reason: "active version failed validation"
413
- }
414
- }
415
- };
416
- }
417
- return {
418
- kind: "baked",
419
- reason: `active version ${broken} failed validation and no valid previous version exists`,
420
- stateToWrite: {
421
- ...next,
422
- currentVersion: null,
423
- rolledBack: {
424
- fromVersion: broken,
425
- toVersion: null,
426
- atMs: now(),
427
- reason: "active version failed validation"
428
- }
429
- }
430
- };
431
- }
432
- return {
433
- kind: "baked",
434
- reason: "no active data-root version",
435
- stateToWrite: changed ? next : null
436
- };
437
- }
438
- var fs3 = __toESM2(__require("fs"));
439
- var path3 = __toESM2(__require("path"));
563
+ var fs4 = __toESM2(__require("fs"));
564
+ var path4 = __toESM2(__require("path"));
440
565
  function detectWorkspaceRoot(fromDir) {
441
- let dir = path3.resolve(fromDir);
566
+ let dir = path4.resolve(fromDir);
442
567
  for (; ; ) {
443
- const pkgPath = path3.join(dir, "package.json");
568
+ const pkgPath = path4.join(dir, "package.json");
444
569
  try {
445
- const raw = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
570
+ const raw = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
446
571
  if (typeof raw === "object" && raw !== null && raw["workspaces"] !== void 0) {
447
572
  return dir;
448
573
  }
449
574
  } catch {
450
575
  }
451
- const parent = path3.dirname(dir);
576
+ const parent = path4.dirname(dir);
452
577
  if (parent === dir) return null;
453
578
  dir = parent;
454
579
  }
455
580
  }
456
- var fs4 = __toESM2(__require("fs"));
457
- var path4 = __toESM2(__require("path"));
581
+ var fs5 = __toESM2(__require("fs"));
582
+ var path5 = __toESM2(__require("path"));
458
583
  var import_node_url = __require("url");
459
584
  var HOST_EXTERNAL_SPECIFIERS = [
460
585
  "@camstack/system",
@@ -482,7 +607,7 @@ var require_dist = __commonJS({
482
607
  return;
483
608
  }
484
609
  const anchorURL = (0, import_node_url.pathToFileURL)(
485
- path4.join(activeRootDir, "node_modules", "__camstack_starter_anchor__.js")
610
+ path5.join(activeRootDir, "node_modules", "__camstack_starter_anchor__.js")
486
611
  ).href;
487
612
  const hooks = {
488
613
  resolve: (specifier, context, nextResolve) => {
@@ -497,23 +622,24 @@ var require_dist = __commonJS({
497
622
  };
498
623
  registerHooks(hooks);
499
624
  }
500
- function readSeedVersion(seedEntry) {
625
+ function readClosureVersion(closureDir, spec) {
501
626
  try {
502
- const pkgJsonPath = path4.join(path4.dirname(seedEntry), "..", "package.json");
503
- const raw = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
627
+ const raw = JSON.parse(
628
+ fs5.readFileSync(path5.join(rootPackageDir(closureDir, spec), "package.json"), "utf-8")
629
+ );
504
630
  if (typeof raw === "object" && raw !== null) {
505
631
  const version = raw["version"];
506
632
  if (typeof version === "string") return version;
507
633
  }
508
- return null;
509
634
  } catch {
510
- return null;
511
635
  }
636
+ return null;
512
637
  }
513
638
  function runNodeStarter(options) {
514
639
  const env = options.env ?? process.env;
515
640
  const now = options.now ?? Date.now;
516
641
  const registerResolver = options.registerResolver ?? registerActiveRootResolver;
642
+ const nodeMajor = options.nodeMajor ?? Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
517
643
  const disabled = env[options.envNames.killSwitch] === "off";
518
644
  const workspaceRoot = detectWorkspaceRoot(options.starterDir);
519
645
  if (workspaceRoot !== null) {
@@ -531,40 +657,34 @@ var require_dist = __commonJS({
531
657
  return;
532
658
  }
533
659
  const rootDir = serverRootDir(options.dataDir);
534
- const state = readServerRootState(rootDir);
535
- const nodeMajor = options.nodeMajor ?? Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
536
- const isValidVersion = (version) => {
537
- const reason = validateVersionDir(rootDir, version, nodeMajor, options.spec);
538
- if (reason !== null) console.warn(`[starter] version ${version} invalid: ${reason}`);
539
- return reason === null;
540
- };
541
- const seedVersion = readSeedVersion(options.seedEntry);
542
- const plan = planBoot(state, isValidVersion, now, seedVersion);
543
- if (plan.stateToWrite !== null) {
544
- try {
545
- writeServerRootState(rootDir, plan.stateToWrite);
546
- } catch (err) {
547
- console.error("[starter] FAILED to persist server-root state \u2014 booting baked seed:", err);
548
- env[options.envNames.bootMode] = "baked";
549
- options.loadEntry(options.seedEntry);
550
- return;
551
- }
552
- if (plan.stateToWrite.rolledBack !== null && (state?.rolledBack ?? null) !== plan.stateToWrite.rolledBack) {
553
- const rb = plan.stateToWrite.rolledBack;
554
- console.warn(
555
- `[starter] ROLLED BACK ${rb.fromVersion} -> ${rb.toVersion ?? "baked seed"}: ${rb.reason}`
556
- );
557
- }
660
+ const swap = applyPendingRootSwap(rootDir, options.spec, nodeMajor, now, (m) => console.log(m));
661
+ if (swap.applied) console.log(`[starter] applied staged update \u2192 ${swap.version ?? "?"}`);
662
+ else if (swap.reason !== "no pending swap")
663
+ console.warn(`[starter] staged update NOT applied: ${swap.reason}`);
664
+ const migration = migrateToSingleCopy(
665
+ rootDir,
666
+ options.spec,
667
+ nodeMajor,
668
+ now,
669
+ (m) => console.log(m)
670
+ );
671
+ if (migration.migrated)
672
+ console.log(`[starter] migrated legacy layout \u2192 single copy (${migration.version ?? "?"})`);
673
+ const activeRootDir = currentDir(rootDir);
674
+ const invalid = validateClosureDir(activeRootDir, nodeMajor, options.spec);
675
+ if (invalid !== null && fs5.existsSync(activeRootDir)) {
676
+ console.warn(`[starter] single copy invalid: ${invalid}`);
558
677
  }
559
- if (plan.kind === "data-root") {
560
- const activeRootDir = versionDir(rootDir, plan.version);
561
- const entry = rootEntryPath(activeRootDir, options.spec);
678
+ const plan = planBoot(invalid === null);
679
+ if (plan.kind === "current") {
680
+ const version = readClosureVersion(activeRootDir, options.spec);
681
+ const entry = currentEntryPath(rootDir, options.spec);
562
682
  console.log(
563
- `[starter] booting ${options.spec.packageName}@${plan.version} from ${activeRootDir}` + (plan.probation ? " (probation boot \u2014 health-check armed)" : "")
683
+ `[starter] booting ${options.spec.packageName}@${version ?? "?"} from ${activeRootDir}`
564
684
  );
565
685
  env[options.envNames.bootMode] = "data-root";
566
686
  env[options.envNames.activeRoot] = activeRootDir;
567
- env[options.envNames.activeVersion] = plan.version;
687
+ if (version !== null) env[options.envNames.activeVersion] = version;
568
688
  registerResolver(activeRootDir);
569
689
  options.loadEntry(entry);
570
690
  return;
@@ -574,8 +694,8 @@ var require_dist = __commonJS({
574
694
  options.loadEntry(options.seedEntry);
575
695
  }
576
696
  var import_node_child_process = __require("child_process");
577
- var fs5 = __toESM2(__require("fs"));
578
- var path5 = __toESM2(__require("path"));
697
+ var fs6 = __toESM2(__require("fs"));
698
+ var path6 = __toESM2(__require("path"));
579
699
  var import_node_util = __require("util");
580
700
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
581
701
  function buildNpmRegistryArgs(registry) {
@@ -585,9 +705,10 @@ var require_dist = __commonJS({
585
705
  var NPM_VIEW_TIMEOUT_MS = 2e4;
586
706
  var NPM_INSTALL_TIMEOUT_MS = 15 * 6e4;
587
707
  var RESTART_REASON_PREFIX = "server-update";
708
+ var STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
588
709
  function readPackageVersion(pkgJsonPath) {
589
710
  try {
590
- const raw = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
711
+ const raw = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
591
712
  if (typeof raw === "object" && raw !== null) {
592
713
  const version = raw["version"];
593
714
  if (typeof version === "string") return version;
@@ -596,7 +717,7 @@ var require_dist = __commonJS({
596
717
  }
597
718
  return null;
598
719
  }
599
- var RootUpdateService = class _RootUpdateService {
720
+ var RootUpdateService = class {
600
721
  spec;
601
722
  envNames;
602
723
  logger;
@@ -614,7 +735,7 @@ var require_dist = __commonJS({
614
735
  this.envNames = options.envNames;
615
736
  this.logger = options.logger;
616
737
  this.restartServerFn = options.restartServer;
617
- this.dataDir = path5.resolve(options.dataDir);
738
+ this.dataDir = path6.resolve(options.dataDir);
618
739
  this.execNpm = options.execNpm ?? (async (args, opts) => {
619
740
  const { stdout } = await execFileAsync("npm", [...args], {
620
741
  cwd: opts.cwd,
@@ -643,57 +764,37 @@ var require_dist = __commonJS({
643
764
  seedVersion() {
644
765
  const seedDir = this.env[this.envNames.seedDir];
645
766
  if (seedDir === void 0 || seedDir.length === 0) return null;
646
- return readPackageVersion(path5.join(seedDir, "package.json"));
767
+ return readPackageVersion(path6.join(seedDir, "package.json"));
647
768
  }
648
769
  rootDir() {
649
770
  return serverRootDir(this.dataDir);
650
771
  }
651
- readState() {
652
- return readServerRootState(this.rootDir()) ?? emptyServerRootState();
653
- }
654
- /**
655
- * Like `readState`, but distinguishes "state.json missing" (normal on a
656
- * fresh node) from "state.json present but corrupt" — in the corrupt case
657
- * the starter booted the baked seed while installed data-dir versions are
658
- * silently ignored, which the status surface must make visible.
659
- */
660
- readStateInfo() {
661
- const rootDir = this.rootDir();
662
- const raw = readServerRootState(rootDir);
663
- if (raw !== null) return { state: raw, corrupt: false };
664
- return { state: emptyServerRootState(), corrupt: fs5.existsSync(stateFilePath(rootDir)) };
665
- }
666
- updateState(state) {
772
+ updateState() {
667
773
  if (this.inFlight === "checking") return "checking";
668
774
  if (this.inFlight === "staging") return "staging";
669
- if (state.pendingBoot !== null) {
670
- const activeVersion = this.env[this.envNames.activeVersion] ?? null;
671
- if (activeVersion !== null && activeVersion === state.pendingBoot.version) {
672
- return "awaiting-confirmation";
673
- }
674
- return "pending-restart";
675
- }
775
+ if (readPendingRootSwap(this.rootDir()) !== null) return "pending-restart";
676
776
  return "idle";
677
777
  }
678
778
  async getServerPackageStatus() {
679
- const { state, corrupt } = this.readStateInfo();
680
779
  const runningVersion = this.runningVersion();
681
780
  const latestVersion = this.checkCache?.latestVersion ?? null;
781
+ const pending = readPendingRootSwap(this.rootDir());
682
782
  const updateAvailable = latestVersion !== null && runningVersion !== null && compareSemver(latestVersion, runningVersion) > 0;
683
783
  return {
684
784
  packageName: this.spec.packageName,
685
785
  runningVersion,
686
786
  nodeRuntimeVersion: process.versions.node,
687
- activeVersion: this.env[this.envNames.activeVersion] ?? state.currentVersion,
688
- previousVersion: state.previousVersion,
787
+ activeVersion: this.env[this.envNames.activeVersion] ?? runningVersion,
788
+ // Single-copy model: no N-1 retention, no rollback record.
789
+ previousVersion: null,
689
790
  seedVersion: this.seedVersion(),
690
791
  latestVersion,
691
792
  updateAvailable,
692
793
  bootMode: this.resolveBootMode(),
693
- updateState: this.updateState(state),
694
- pendingVersion: state.pendingBoot?.version ?? null,
695
- rolledBack: state.rolledBack,
696
- stateFileCorrupt: corrupt,
794
+ updateState: this.updateState(),
795
+ pendingVersion: pending?.version ?? null,
796
+ rolledBack: null,
797
+ stateFileCorrupt: false,
697
798
  lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null
698
799
  };
699
800
  }
@@ -770,13 +871,13 @@ var require_dist = __commonJS({
770
871
  message: `Refused: another operation is in flight (${this.inFlight}).`
771
872
  };
772
873
  }
773
- const stateBefore = this.readState();
774
- if (stateBefore.pendingBoot !== null) {
874
+ const pendingBefore = readPendingRootSwap(this.rootDir());
875
+ if (pendingBefore !== null) {
775
876
  return {
776
877
  accepted: false,
777
878
  targetVersion: input.version ?? null,
778
879
  restarting: false,
779
- message: `Refused: version ${stateBefore.pendingBoot.version} is already staged and awaiting restart.`
880
+ message: `Refused: version ${pendingBefore.version} is already staged and awaiting restart.`
780
881
  };
781
882
  }
782
883
  let target = input.version ?? null;
@@ -803,7 +904,7 @@ var require_dist = __commonJS({
803
904
  }
804
905
  if (isDevChannelVersion(target)) {
805
906
  const uploadsDir = devUploadVersionDir(this.rootDir(), target);
806
- if (!fs5.existsSync(uploadsDir)) {
907
+ if (!fs6.existsSync(uploadsDir)) {
807
908
  return {
808
909
  accepted: false,
809
910
  targetVersion: target,
@@ -813,8 +914,9 @@ var require_dist = __commonJS({
813
914
  }
814
915
  }
815
916
  this.inFlight = "staging";
917
+ let stagingPath;
816
918
  try {
817
- await this.stageAndActivate(target);
919
+ stagingPath = await this.stageClosure(target);
818
920
  } catch (err) {
819
921
  const message = err instanceof Error ? err.message : String(err);
820
922
  this.logger.error("root package update staging failed", {
@@ -829,45 +931,42 @@ var require_dist = __commonJS({
829
931
  } finally {
830
932
  this.inFlight = "idle";
831
933
  }
832
- const state = this.readState();
833
- writeServerRootState(this.rootDir(), {
834
- ...state,
835
- pendingBoot: {
836
- version: target,
837
- fromVersion: state.currentVersion,
838
- requestedAtMs: this.now(),
839
- bootAttempts: 0
840
- },
841
- rolledBack: null
934
+ writePendingRootSwap(this.rootDir(), {
935
+ schemaVersion: 1,
936
+ version: target,
937
+ stagingPath,
938
+ requestedAtMs: this.now()
842
939
  });
843
940
  this.logger.info("root package update staged \u2014 restarting to apply", {
844
- meta: { targetVersion: target, fromVersion: state.currentVersion }
941
+ meta: { targetVersion: target, fromVersion: runningVersion }
845
942
  });
846
943
  this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}`);
847
944
  return {
848
945
  accepted: true,
849
946
  targetVersion: target,
850
947
  restarting: true,
851
- message: `Staged ${this.spec.packageName}@${target} \u2014 restarting to apply (probation boot with auto-rollback).`
948
+ message: `Staged ${this.spec.packageName}@${target} \u2014 restarting to apply (single automatic reboot).`
852
949
  };
853
950
  }
854
951
  /**
855
- * npm-install the target closure into a SAME-FS staging dir inside
856
- * `versions/`, validate it, then atomically rename it into place.
952
+ * npm-install the target closure into a SAME-FS `.staging-*` dir, then
953
+ * validate it (entry + version + native prebuilds). Returns the staging dir
954
+ * path for the pending-root-swap marker; the STARTER swaps it into `current`
955
+ * on the next boot. Throws (cleaning up the staging dir) on any failure so
956
+ * `applyServerUpdate` reports a staging error and never arms a bad swap.
857
957
  */
858
- async stageAndActivate(target) {
958
+ async stageClosure(target) {
859
959
  const rootDir = this.rootDir();
860
- const vDir = versionsDir(rootDir);
861
- fs5.mkdirSync(vDir, { recursive: true });
862
- const stagingDir = path5.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`);
863
- fs5.mkdirSync(stagingDir, { recursive: true });
960
+ fs6.mkdirSync(rootDir, { recursive: true });
961
+ const stagingDir = stagingDirPath(rootDir, target, process.pid, this.now());
962
+ fs6.mkdirSync(stagingDir, { recursive: true });
864
963
  try {
865
964
  const devDir = devUploadVersionDir(rootDir, target);
866
965
  const registry = this.env["CAMSTACK_NPM_REGISTRY"];
867
966
  const installArgs = ["install", "--omit=dev", "--no-audit", "--no-fund", "--loglevel=error"];
868
- if (fs5.existsSync(devDir)) {
869
- fs5.writeFileSync(
870
- path5.join(stagingDir, "package.json"),
967
+ if (fs6.existsSync(devDir)) {
968
+ fs6.writeFileSync(
969
+ path6.join(stagingDir, "package.json"),
871
970
  JSON.stringify(this.buildDevUploadsPackageJson(devDir, target), null, 2),
872
971
  "utf-8"
873
972
  );
@@ -876,8 +975,8 @@ var require_dist = __commonJS({
876
975
  timeoutMs: NPM_INSTALL_TIMEOUT_MS
877
976
  });
878
977
  } else {
879
- fs5.writeFileSync(
880
- path5.join(stagingDir, "package.json"),
978
+ fs6.writeFileSync(
979
+ path6.join(stagingDir, "package.json"),
881
980
  JSON.stringify({ name: "camstack-node-root", private: true }, null, 2),
882
981
  "utf-8"
883
982
  );
@@ -887,37 +986,32 @@ var require_dist = __commonJS({
887
986
  );
888
987
  }
889
988
  const entry = rootEntryPath(stagingDir, this.spec);
890
- if (!fs5.existsSync(entry)) {
989
+ if (!fs6.existsSync(entry)) {
891
990
  throw new Error(`staged closure is missing the root entry (${entry})`);
892
991
  }
893
- const stagedVersion = readPackageVersion(
894
- path5.join(rootPackageDir(stagingDir, this.spec), "package.json")
895
- );
896
- if (stagedVersion !== target) {
992
+ const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
993
+ const invalid = validateClosureDir(stagingDir, nodeMajor, this.spec, target);
994
+ if (invalid !== null) {
995
+ throw new Error(`staged closure invalid: ${invalid}`);
996
+ }
997
+ const missingNatives = findMissingNativePrebuilds(stagingDir);
998
+ if (missingNatives.length > 0) {
897
999
  throw new Error(
898
- `staged closure version mismatch: expected ${target}, got ${stagedVersion ?? "unknown"}`
1000
+ `staged closure missing native prebuilds: ${missingNatives.join(", ")} \u2014 refusing to arm a swap that would break the forked runners`
899
1001
  );
900
1002
  }
901
- const dest = versionDir(rootDir, target);
902
- if (fs5.existsSync(dest)) {
903
- const aside = `${dest}.evicted-${this.now()}`;
904
- await fs5.promises.rename(dest, aside);
905
- await fs5.promises.rm(aside, { recursive: true, force: true }).catch(() => void 0);
906
- }
907
- await fs5.promises.rename(stagingDir, dest);
1003
+ return stagingDir;
908
1004
  } catch (err) {
909
- await fs5.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
1005
+ await fs6.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
910
1006
  throw err;
911
1007
  }
912
1008
  }
913
1009
  /**
914
1010
  * Synthetic staging package.json for the dev server-deploy channel:
915
1011
  * `dependencies` carries the root package as a `file:` ref to its uploaded
916
- * tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz
917
- * so any transitive occurrence resolves to the uploaded copy, never npm.
1012
+ * tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz.
918
1013
  * Fail-closed: a missing/invalid manifest, a manifest for a different
919
- * version, a missing root package, or a listed-but-absent tgz all throw
920
- * (surfaced by `applyServerUpdate` as a staging failure).
1014
+ * version, a missing root package, or a listed-but-absent tgz all throw.
921
1015
  */
922
1016
  buildDevUploadsPackageJson(devDir, target) {
923
1017
  const manifest = readDevUploadManifest(devDir);
@@ -936,8 +1030,8 @@ var require_dist = __commonJS({
936
1030
  );
937
1031
  }
938
1032
  const fileRef = (filename) => {
939
- const abs = path5.join(devDir, filename);
940
- if (!fs5.existsSync(abs)) {
1033
+ const abs = path6.join(devDir, filename);
1034
+ if (!fs6.existsSync(abs)) {
941
1035
  throw new Error(`dev-uploads tarball listed in the manifest is missing: ${abs}`);
942
1036
  }
943
1037
  return `file:${abs}`;
@@ -954,77 +1048,28 @@ var require_dist = __commonJS({
954
1048
  ...Object.keys(overrides).length > 0 ? { overrides } : {}
955
1049
  };
956
1050
  }
957
- // ── Rollback ──────────────────────────────────────────────────────────
1051
+ // ── Rollback (removed — single-copy has no N-1) ────────────────────────
1052
+ /**
1053
+ * Rollback is NOT supported in the single-copy model — there is no retained
1054
+ * N-1 copy to revert to (operator's accepted tradeoff). Recovery is manual:
1055
+ * re-apply a known-good version via {@link applyServerUpdate}, or reinstall
1056
+ * the image seed. Kept on the surface so the cap contract is unchanged.
1057
+ */
958
1058
  async rollbackServerUpdate() {
959
- if (this.inFlight !== "idle") {
960
- return {
961
- accepted: false,
962
- targetVersion: null,
963
- restarting: false,
964
- message: `Refused: another operation is in flight (${this.inFlight}).`
965
- };
966
- }
967
- const state = this.readState();
968
- if (state.pendingBoot !== null) {
969
- return {
970
- accepted: false,
971
- targetVersion: null,
972
- restarting: false,
973
- message: `Refused: version ${state.pendingBoot.version} is already staged and awaiting restart.`
974
- };
975
- }
976
- if (state.currentVersion === null || state.previousVersion === null) {
977
- return {
978
- accepted: false,
979
- targetVersion: state.previousVersion,
980
- restarting: false,
981
- message: "Refused: no previous version available to roll back to."
982
- };
983
- }
984
- const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
985
- const invalidReason = validateVersionDir(
986
- this.rootDir(),
987
- state.previousVersion,
988
- nodeMajor,
989
- this.spec
990
- );
991
- if (invalidReason !== null) {
992
- return {
993
- accepted: false,
994
- targetVersion: state.previousVersion,
995
- restarting: false,
996
- message: `Refused: previous version ${state.previousVersion} is not loadable (${invalidReason}).`
997
- };
998
- }
999
- writeServerRootState(this.rootDir(), {
1000
- ...state,
1001
- pendingBoot: {
1002
- version: state.previousVersion,
1003
- fromVersion: state.currentVersion,
1004
- requestedAtMs: this.now(),
1005
- bootAttempts: 0
1006
- }
1007
- });
1008
- this.logger.info("root package rollback requested \u2014 restarting", {
1009
- meta: { fromVersion: state.currentVersion, toVersion: state.previousVersion }
1010
- });
1011
- this.restartServerFn(
1012
- `${RESTART_REASON_PREFIX}: rollback to ${this.spec.packageName}@${state.previousVersion}`
1013
- );
1014
1059
  return {
1015
- accepted: true,
1016
- targetVersion: state.previousVersion,
1017
- restarting: true,
1018
- message: `Rolling back to ${this.spec.packageName}@${state.previousVersion} \u2014 restarting.`
1060
+ accepted: false,
1061
+ targetVersion: null,
1062
+ restarting: false,
1063
+ message: "Rollback is not supported in the single-copy model (no retained previous version). Recover by re-applying a known-good version via applyServerUpdate, or reinstall the image seed."
1019
1064
  };
1020
1065
  }
1021
1066
  // ── Plain restart ─────────────────────────────────────────────────────
1022
1067
  /**
1023
- * Plain process restart — no version change. Backs the `restartServer` cap
1024
- * method. Guards mirror apply/rollback: refuse while a stage is in flight
1025
- * (a concurrent npm install must not be interrupted) or while a version is
1026
- * already staged awaiting restart (a naive bounce would boot the OLD version
1027
- * and orphan the pending one — the operator should apply/rollback instead).
1068
+ * Plain process restart — no version change. Refuse while a stage is in
1069
+ * flight (a concurrent npm install must not be interrupted) or while a
1070
+ * version is already staged awaiting restart (a naive bounce would boot the
1071
+ * OLD version and orphan the pending swap the operator should apply
1072
+ * instead).
1028
1073
  */
1029
1074
  restartNode() {
1030
1075
  const runningVersion = this.runningVersion();
@@ -1036,13 +1081,12 @@ var require_dist = __commonJS({
1036
1081
  message: `Refused: another operation is in flight (${this.inFlight}).`
1037
1082
  };
1038
1083
  }
1039
- const state = this.readState();
1040
- if (state.pendingBoot !== null) {
1084
+ if (readPendingRootSwap(this.rootDir()) !== null) {
1041
1085
  return {
1042
1086
  accepted: false,
1043
1087
  targetVersion: runningVersion,
1044
1088
  restarting: false,
1045
- message: `Refused: version ${state.pendingBoot.version} is staged and awaiting restart \u2014 apply or roll it back instead of a plain restart.`
1089
+ message: "Refused: a version is staged and awaiting restart \u2014 apply it instead of a plain restart."
1046
1090
  };
1047
1091
  }
1048
1092
  this.logger.info("plain node restart requested", { meta: { runningVersion } });
@@ -1056,103 +1100,35 @@ var require_dist = __commonJS({
1056
1100
  }
1057
1101
  // ── Boot health confirmation ──────────────────────────────────────────
1058
1102
  /**
1059
- * Called by the post-boot path once the node is READY (hub: post-boot
1060
- * pipeline; agent: first acked `$hub.registerNode`). When THIS process is
1061
- * the probation boot of a pending version, promote it: `currentVersion`
1062
- * pending, `previousVersion` the version it replaced (N-1 retained for
1063
- * rollback), clear `pendingBoot` + `rolledBack`, and prune every version
1064
- * dir beyond {current, previous}. No-op otherwise.
1103
+ * Called by the post-boot path once the node is READY. In the single-copy
1104
+ * model there is no pending version to promote (the swap already happened in
1105
+ * the starter). This becomes a best-effort GC of orphaned transient dirs
1106
+ * (`.staging-*` / `.trash-*` left by a crash mid-swap) + the dev-uploads
1107
+ * sweep. Kept returning the legacy `{ promoted }` shape for the callers.
1065
1108
  */
1066
1109
  confirmBootHealthy() {
1067
- const state = readServerRootState(this.rootDir());
1068
- if (state === null || state.pendingBoot === null) return { promoted: null };
1069
- const activeVersion = this.env[this.envNames.activeVersion] ?? null;
1070
- if (activeVersion !== state.pendingBoot.version) {
1071
- return { promoted: null };
1072
- }
1073
- const promoted = {
1074
- ...state,
1075
- currentVersion: state.pendingBoot.version,
1076
- previousVersion: state.pendingBoot.fromVersion,
1077
- pendingBoot: null,
1078
- rolledBack: null
1079
- };
1080
- writeServerRootState(this.rootDir(), promoted);
1081
- this.pruneVersions(
1082
- [promoted.currentVersion, promoted.previousVersion].filter(
1083
- (v) => typeof v === "string"
1084
- )
1085
- );
1110
+ sweepTransientRootDirs(this.rootDir(), this.now(), STALE_TRANSIENT_MS);
1086
1111
  this.sweepDevUploads(DEV_UPLOADS_KEEP_COUNT);
1087
- this.logger.info("root package update confirmed healthy", {
1088
- meta: {
1089
- version: promoted.currentVersion,
1090
- previousVersion: promoted.previousVersion
1091
- }
1092
- });
1093
- return { promoted: promoted.currentVersion };
1094
- }
1095
- /**
1096
- * Remove version dirs under `versions/` not in `keep`.
1097
- *
1098
- * Transient dot-entries (`.staging-*` / `.evicted-*`) are NOT pruned unless
1099
- * older than {@link RootUpdateService.STALE_TRANSIENT_MS}: the API starts
1100
- * serving BEFORE the post-boot `confirmBootHealthy()` runs, so a concurrent
1101
- * `applyServerUpdate` can be mid-npm-install into a fresh `.staging-*` dir
1102
- * when this prune fires — deleting it would corrupt that install. Fresh
1103
- * transient dirs are owned by their in-flight operation (which cleans up on
1104
- * failure); the staleness sweep only collects dirs orphaned by a crash.
1105
- */
1106
- pruneVersions(keep) {
1107
- const vDir = versionsDir(this.rootDir());
1108
- let entries;
1109
- try {
1110
- entries = fs5.readdirSync(vDir);
1111
- } catch {
1112
- return;
1113
- }
1114
- for (const entry of entries) {
1115
- if (keep.includes(entry)) continue;
1116
- const full = path5.join(vDir, entry);
1117
- if (entry.startsWith(".")) {
1118
- try {
1119
- const ageMs = this.now() - fs5.statSync(full).mtimeMs;
1120
- if (ageMs < _RootUpdateService.STALE_TRANSIENT_MS) continue;
1121
- } catch {
1122
- continue;
1123
- }
1124
- }
1125
- try {
1126
- fs5.rmSync(full, { recursive: true, force: true });
1127
- this.logger.debug("pruned server-root version dir", { meta: { entry } });
1128
- } catch (err) {
1129
- this.logger.warn("failed to prune server-root version dir", {
1130
- meta: { entry, error: err instanceof Error ? err.message : String(err) }
1131
- });
1132
- }
1133
- }
1112
+ return { promoted: null };
1134
1113
  }
1135
1114
  /**
1136
1115
  * Promote-time sweep of `server-root/dev-uploads/`: keep only the
1137
1116
  * `keepCount` most recent entries (ordered by the self-describing
1138
1117
  * `-dev.<epochSeconds>` suffix; non-dev-shaped residue counts as oldest).
1139
- * Uses the same rename-aside graveyard pattern as the addon installer —
1140
- * a rename is a metadata op that succeeds even when a tarball is held
1141
- * open, and residue in the graveyard is re-swept on the next promote.
1142
1118
  */
1143
1119
  sweepDevUploads(keepCount) {
1144
1120
  const dir = devUploadsDir(this.rootDir());
1145
1121
  let entries;
1146
1122
  try {
1147
- entries = fs5.readdirSync(dir);
1123
+ entries = fs6.readdirSync(dir);
1148
1124
  } catch {
1149
1125
  return;
1150
1126
  }
1151
- const graveyard = path5.join(dir, ".sweeping");
1127
+ const graveyard = path6.join(dir, ".sweeping");
1152
1128
  try {
1153
- for (const residue of fs5.readdirSync(graveyard)) {
1129
+ for (const residue of fs6.readdirSync(graveyard)) {
1154
1130
  try {
1155
- fs5.rmSync(path5.join(graveyard, residue), { recursive: true, force: true });
1131
+ fs6.rmSync(path6.join(graveyard, residue), { recursive: true, force: true });
1156
1132
  } catch {
1157
1133
  }
1158
1134
  }
@@ -1164,11 +1140,11 @@ var require_dist = __commonJS({
1164
1140
  );
1165
1141
  const doomed = byNewestFirst.slice(keepCount);
1166
1142
  if (doomed.length === 0) return;
1167
- fs5.mkdirSync(graveyard, { recursive: true });
1143
+ fs6.mkdirSync(graveyard, { recursive: true });
1168
1144
  for (const entry of doomed) {
1169
- const aside = path5.join(graveyard, `${entry}-${this.now()}`);
1145
+ const aside = path6.join(graveyard, `${entry}-${this.now()}`);
1170
1146
  try {
1171
- fs5.renameSync(path5.join(dir, entry), aside);
1147
+ fs6.renameSync(path6.join(dir, entry), aside);
1172
1148
  } catch (err) {
1173
1149
  this.logger.warn("failed to move dev-uploads entry aside for sweep", {
1174
1150
  meta: { entry, error: err instanceof Error ? err.message : String(err) }
@@ -1176,14 +1152,12 @@ var require_dist = __commonJS({
1176
1152
  continue;
1177
1153
  }
1178
1154
  try {
1179
- fs5.rmSync(aside, { recursive: true, force: true });
1155
+ fs6.rmSync(aside, { recursive: true, force: true });
1180
1156
  this.logger.debug("swept dev-uploads entry", { meta: { entry } });
1181
1157
  } catch {
1182
1158
  }
1183
1159
  }
1184
1160
  }
1185
- /** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */
1186
- static STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
1187
1161
  };
1188
1162
  }
1189
1163
  });
@@ -1193,4 +1167,4 @@ export {
1193
1167
  __toESM,
1194
1168
  require_dist
1195
1169
  };
1196
- //# sourceMappingURL=chunk-KPUXW5YX.mjs.map
1170
+ //# sourceMappingURL=chunk-DIBZT6GT.mjs.map