@camstack/server 1.1.70 → 1.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,7 +39,6 @@ exports.shouldEmitProviderRegisteredReady = shouldEmitProviderRegisteredReady;
39
39
  const os = __importStar(require("node:os"));
40
40
  const addon_row_manifest_1 = require("./addon-row-manifest");
41
41
  const runner_convergence_1 = require("./runner-convergence");
42
- const system_addons_sync_1 = require("./system-addons-sync");
43
42
  const package_dir_utils_1 = require("./package-dir-utils");
44
43
  const types_1 = require("@camstack/types");
45
44
  const system_1 = require("@camstack/system");
@@ -435,25 +434,30 @@ class AddonRegistryService {
435
434
  // launcher). Defaults to `<dataDir>/addons` for dev.
436
435
  const dataDir = path.resolve(process.env.CAMSTACK_DATA ?? 'camstack-data');
437
436
  const addonsDir = process.env.CAMSTACK_ADDONS_DIR ?? path.resolve(dataDir, 'addons');
438
- // Builtins load from the ADDONS-DIR copy of `@camstack/system`, which no
439
- // update channel refreshes on an image hub (seed excludes it, apply-update
440
- // swaps only server-root, deploy of system is rejected)a stale copy
441
- // silently pins builtin code forever (2026-07-17: snapshot builtin ran
442
- // 1.1.49 while the closure carried 1.1.51). Reconcile it from the hub's
443
- // own closure when the closure is NEWER; a newer addons copy (dev
444
- // live-update flow) is left alone. Never throws.
437
+ // Single-copy model (2026-07-18 collapse): the framework BUILTINS
438
+ // (`@camstack/system`: device-manager, snapshot, storage, settings, …) load
439
+ // from the ACTIVE CLOSURE the hub itself runs from resolved via the same
440
+ // walk-up hub-main uses NOT from a physical `/data/addons/@camstack/system`
441
+ // copy. That copy drifted because no update channel refreshed it (the
442
+ // recurring "+1 restart" / stale-builtin pain); there is now ONE copy and no
443
+ // `syncSystemAddonsCopy` race. Non-framework addons still load from the
444
+ // addons dir unchanged. The addons-dir `@camstack/system` copy (if present,
445
+ // seeded by the image entrypoint) is SKIPPED to avoid a double-load.
445
446
  const closureSystemDir = (0, package_dir_utils_1.resolveHubClosurePackageDir)('@camstack/system');
446
447
  if (closureSystemDir) {
447
- const outcome = (0, system_addons_sync_1.syncSystemAddonsCopy)({
448
- addonsDir,
449
- closureSystemDir,
450
- log: (msg, meta) => this.logger.info(msg, { meta: meta ?? {} }),
451
- });
452
- if (outcome === 'failed') {
453
- this.logger.warn('system addons-copy sync failed — builtins may run stale code');
448
+ try {
449
+ await this.addonLoader.loadFromAddonDir(closureSystemDir);
454
450
  }
451
+ catch (err) {
452
+ this.logger.error('Failed to load @camstack/system builtins from the active closure', {
453
+ meta: { closureSystemDir, error: err instanceof Error ? err.message : String(err) },
454
+ });
455
+ }
456
+ }
457
+ else {
458
+ this.logger.warn('@camstack/system not resolvable from the hub closure — builtins may be missing');
455
459
  }
456
- await this.addonLoader.loadFromDirectory(addonsDir);
460
+ await this.addonLoader.loadFromDirectory(addonsDir, ['@camstack/system']);
457
461
  // Hand pre-init load failures (broken package.json, missing
458
462
  // dist, import-time errors) to the health monitor so its retry
459
463
  // loop and post-grace alerting cover them. Failures during the
@@ -494,7 +498,12 @@ class AddonRegistryService {
494
498
  packageDisplayName: registered.packageDisplayName,
495
499
  ...(registered.bundle !== undefined ? { bundle: registered.bundle } : {}),
496
500
  declaredCapabilities: declCaps,
497
- addonDir: path.join(addonsDir, registered.packageName),
501
+ // @camstack/system builtins load from the active closure (single-copy
502
+ // model), not the inert addons-dir copy — point their dir there so
503
+ // any dir-derived path (data dir, markers) resolves to the real copy.
504
+ addonDir: registered.packageName === '@camstack/system' && closureSystemDir
505
+ ? closureSystemDir
506
+ : path.join(addonsDir, registered.packageName),
498
507
  declaration: registered.declaration,
499
508
  });
500
509
  }
@@ -38,18 +38,18 @@ exports.ServerUpdateService = void 0;
38
38
  * ServerUpdateService — hub-side adapter over the SHARED `RootUpdateService`
39
39
  * (`@camstack/node-root`, bundled into `dist/server-root/index.js`).
40
40
  *
41
- * The stage/apply/rollback/probation engine — including the security-review
42
- * fixes (rollback reentrancy guards, fresh-transient prune protection,
43
- * `stateFileCorrupt` visibility) lives in the shared core so the hub and
44
- * the agent can never drift. This class only binds the hub's parameters:
45
- * the `@camstack/server` root spec, the `CAMSTACK_SERVER_*` env markers,
46
- * the `CAMSTACK_DATA` data-dir default and the running package.json path.
41
+ * The single-copy stage/apply engine — stage a closure into `.staging-*`,
42
+ * arm the pending-root-swap marker, restart lives in the shared core so the
43
+ * hub and the agent can never drift. This class only binds the hub's
44
+ * parameters: the `@camstack/server` root spec, the `CAMSTACK_SERVER_*` env
45
+ * markers, the `CAMSTACK_DATA` data-dir default and the running package.json.
47
46
  *
48
- * The starter (server/backend/src/starter.ts → shared `runNodeStarter`)
49
- * owns the OTHER half: choosing the closure at boot, bumping the probation
50
- * counter, auto-rolling-back a version whose probation boot never confirmed.
47
+ * The starter (server/backend/src/starter.ts → shared `runNodeStarter`) owns
48
+ * the OTHER half: applying the staged swap of the single `current/` copy at
49
+ * boot + the one-time migration off the legacy multi-copy layout. There is no
50
+ * probation boot / N-1 / auto-rollback (2026-07-18 collapse).
51
51
  *
52
- * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md
52
+ * Spec: docs/superpowers/specs/2026-07-18-single-framework-copy-collapse-design.md
53
53
  */
54
54
  const path = __importStar(require("node:path"));
55
55
  const index_js_1 = require("../../server-root/index.js");
package/dist/launcher.js CHANGED
@@ -254,6 +254,19 @@ async function launch() {
254
254
  if (fsw.applied)
255
255
  console.log(`[launcher] Applied staged framework update (job ${fsw.jobId ?? '?'})`);
256
256
  // ── End framework swap ────────────────────────────────────────────────────
257
+ // ── Single-copy framework dir (2026-07-18 collapse) ────────────────────────
258
+ // In data-root mode the ACTIVE single copy (set by the starter as
259
+ // CAMSTACK_SERVER_ACTIVE_ROOT) IS the framework — point the forked
260
+ // addon-runners' resolver + NODE_PATH at it instead of the (now-inert)
261
+ // static /data/framework copy. Forked children inherit this via process.env,
262
+ // and the NODE_PATH extension below reads CAMSTACK_FRAMEWORK_DIR. No-op in
263
+ // baked mode (activeRoot unset → keep the image's /data/framework).
264
+ const activeRoot = process.env['CAMSTACK_SERVER_ACTIVE_ROOT'];
265
+ if (activeRoot && fs.existsSync(path.join(activeRoot, 'node_modules', '@camstack', 'system'))) {
266
+ process.env['CAMSTACK_FRAMEWORK_DIR'] = activeRoot;
267
+ console.log(`[launcher] Single-copy framework dir → ${activeRoot}`);
268
+ }
269
+ // ──────────────────────────────────────────────────────────────────────────
257
270
  // Restore must happen first so the snapshot's `addons/` is what the
258
271
  // installer + symlink step pick up — otherwise we'd install a stale
259
272
  // set and overwrite it a step later.