@camstack/server 1.2.70 → 1.2.72

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.
package/dist/launcher.js CHANGED
@@ -52,10 +52,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
52
52
  * node-root `applyServerUpdate` flow, not an in-launcher package swap.
53
53
  */
54
54
  const fs = __importStar(require("node:fs"));
55
+ const os = __importStar(require("node:os"));
55
56
  const path = __importStar(require("node:path"));
56
57
  const tar = __importStar(require("tar"));
57
58
  const yaml = __importStar(require("js-yaml"));
58
59
  const framework_nodepath_js_1 = require("./framework-nodepath.js");
60
+ const addon_root_inventory_js_1 = require("./addon-root-inventory.js");
59
61
  const package_inventory_js_1 = require("./package-inventory.js");
60
62
  const bootstrap_packages_js_1 = require("./bootstrap-packages.js");
61
63
  /** Path of the manifest file embedded inside every archive. */
@@ -340,11 +342,14 @@ async function launch() {
340
342
  const bootstrapRequired = readBootstrapRequiredAddons(dataDir, bootstrapSchema) ?? roleDefaultBootstrap;
341
343
  console.log(`[launcher] bootstrap (${role}): ${bootstrapRequired.length} required package(s)`);
342
344
  await installer.ensureRequiredPackages(bootstrapRequired);
343
- // Reconcile the install manifest with what is on disk. Addons baked
344
- // into the image are copied straight into addonsDir by the container
345
- // entrypoint and never pass through an install codepath, so they are
346
- // absent from manifest.json which makes runtime "Update" reject them
347
- // with "not currently tracked in manifest". Idempotent; safe every boot.
345
+ // Reconcile the install manifest against what is on disk the directory is
346
+ // the truth, because it is what the loader runs. Registers image-seeded
347
+ // addons (copied straight into addonsDir by the container entrypoint, never
348
+ // through an install codepath, so runtime "Update" rejected them as "not
349
+ // currently tracked"), corrects a recorded version that disagrees with the
350
+ // installed package.json, and drops entries naming a directory that does not
351
+ // exist. Idempotent; safe every boot. Runs BEFORE any update can be in
352
+ // flight, and never touches an entry holding a rollback pointer.
348
353
  //
349
354
  // Guarded with a typeof check: the backend and @camstack/system normally
350
355
  // ship together in one image, but a system-only framework update can
@@ -354,7 +359,7 @@ async function launch() {
354
359
  if (typeof installer.reconcileManifest === 'function') {
355
360
  const reconciled = installer.reconcileManifest();
356
361
  if (reconciled > 0) {
357
- console.log(`[launcher] Manifest reconciled — ${reconciled} seeded addon(s) registered`);
362
+ console.log(`[launcher] Manifest reconciled — ${reconciled} correction(s)`);
358
363
  }
359
364
  }
360
365
  else {
@@ -494,6 +499,72 @@ async function launch() {
494
499
  // to boot when it is the thing that was supposed to explain failures.
495
500
  console.warn(`[launcher] package inventory failed: ${err instanceof Error ? err.message : String(err)}`);
496
501
  }
502
+ // A SECOND ADDON ROOT on this machine.
503
+ //
504
+ // The inventory above scans roots derived from the ACTIVE data dir, so an
505
+ // entire rival data root — a retired Electron app id, the pre-Electron
506
+ // `~/camstack-agent-data` — is structurally invisible to it. Those trees hold
507
+ // complete `addons/@camstack/` installs with plausible package.json files,
508
+ // and reading a version out of one answers a question about a node that has
509
+ // not run since June. Measured on the Mac agent: four addon roots, four
510
+ // truthful `addon-pipeline` versions, one of them the running node's.
511
+ //
512
+ // Reported, never fatal, never deleted here — same policy as D45. Making the
513
+ // second location LOUD is the whole job; removing it stays an operator
514
+ // decision. `CAMSTACK_INVENTORY_STRICT=1` makes it fatal, which is how a
515
+ // machine that HAS been cleaned keeps itself clean.
516
+ try {
517
+ const addonRoots = (0, addon_root_inventory_js_1.inventoryAddonRoots)({
518
+ dataDir: DATA_DIR,
519
+ activeAddonsDir: addonsDir,
520
+ homeDir: os.homedir(),
521
+ platform: process.platform,
522
+ }, {
523
+ exists: (dir) => fs.existsSync(dir),
524
+ listPackages: (scopeDir) => {
525
+ try {
526
+ return fs
527
+ .readdirSync(scopeDir, { withFileTypes: true })
528
+ .filter((e) => e.isDirectory() || e.isSymbolicLink())
529
+ .map((e) => e.name);
530
+ }
531
+ catch {
532
+ return [];
533
+ }
534
+ },
535
+ readVersion: (packageDir) => {
536
+ try {
537
+ const raw = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'));
538
+ const version = typeof raw === 'object' && raw !== null
539
+ ? raw.version
540
+ : undefined;
541
+ return typeof version === 'string' ? version : null;
542
+ }
543
+ catch {
544
+ return null;
545
+ }
546
+ },
547
+ realPath: (dir) => {
548
+ try {
549
+ return fs.realpathSync(dir);
550
+ }
551
+ catch {
552
+ return dir;
553
+ }
554
+ },
555
+ });
556
+ const addonRootReport = (0, addon_root_inventory_js_1.formatAddonRootReport)(addonRoots);
557
+ if (addonRootReport !== '') {
558
+ console.error(`[launcher] ${addonRootReport}`);
559
+ if (process.env['CAMSTACK_INVENTORY_STRICT'] === '1') {
560
+ console.error('[launcher] CAMSTACK_INVENTORY_STRICT=1 — refusing to boot on this layout');
561
+ process.exit(1);
562
+ }
563
+ }
564
+ }
565
+ catch (err) {
566
+ console.warn(`[launcher] addon-root inventory failed: ${err instanceof Error ? err.message : String(err)}`);
567
+ }
497
568
  // Now safe to load the role's runtime entry (both have static imports from
498
569
  // @camstack/system, resolved only after the framework dir + NODE_PATH setup
499
570
  // above). The agent boots from THIS same @camstack/server closure — there is
package/dist/main.js CHANGED
@@ -276,7 +276,10 @@ async function bootstrap() {
276
276
  const uploadAddonRegistry = app.get(addon_registry_service_1.AddonRegistryService);
277
277
  const uploadAddonPackage = app.get(addon_package_service_1.AddonPackageService);
278
278
  const uploadLogger = app.get(logging_service_1.LoggingService).createLogger('addon-upload');
279
- await (0, addon_upload_1.registerAddonUploadRoute)(fastify, uploadAddonBridge, uploadAuthService, uploadMoleculer, uploadAddonRegistry, uploadAddonPackage, uploadLogger);
279
+ // The delivery ledger marks "the hub is writing addons to this node" for
280
+ // the whole deploy+reload, so the addon back-fill cannot read the roster
281
+ // gap a deploy opens and repair the node over the bytes just shipped.
282
+ await (0, addon_upload_1.registerAddonUploadRoute)(fastify, uploadAddonBridge, uploadAuthService, uploadMoleculer, uploadAddonRegistry, uploadAddonPackage, uploadLogger, app.get(agent_registry_service_1.AgentRegistryService).deliveryLedger);
280
283
  console.log('[bootstrap] Addon upload route registered at POST /api/addons/upload');
281
284
  // Dev server-deploy channel gateway (`camstack deploy-server`). MUST be
282
285
  // registered AFTER registerAddonUploadRoute — it reuses the multipart
@@ -44,37 +44,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  };
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.bootManual = bootManual;
47
- const path = __importStar(require("node:path"));
48
47
  const node_crypto_1 = require("node:crypto");
49
- const fastify_1 = __importDefault(require("fastify"));
48
+ const path = __importStar(require("node:path"));
50
49
  const types_1 = require("@camstack/types");
51
- const lifecycle_runner_singleton_1 = require("./core/lifecycle/lifecycle-runner.singleton");
50
+ const fastify_1 = __importDefault(require("fastify"));
51
+ const addon_upload_1 = require("./api/addon-upload");
52
+ const cap_providers_1 = require("./api/core/cap-providers");
53
+ const post_boot_service_1 = require("./boot/post-boot.service");
54
+ const addon_package_service_1 = require("./core/addon/addon-package.service");
55
+ const addon_registry_service_1 = require("./core/addon/addon-registry.service");
56
+ const addon_search_service_1 = require("./core/addon/addon-search.service");
57
+ const addon_bridge_service_1 = require("./core/addon-bridge/addon-bridge.service");
58
+ const addon_pages_service_1 = require("./core/addon-pages/addon-pages.service");
59
+ const addon_widgets_service_1 = require("./core/addon-widgets/addon-widgets.service");
60
+ const agent_registry_service_1 = require("./core/agent/agent-registry.service");
61
+ const cluster_node_history_store_1 = require("./core/agent/cluster-node-history-store");
62
+ const auth_service_1 = require("./core/auth/auth.service");
63
+ const capability_service_1 = require("./core/capability/capability.service");
52
64
  const config_service_1 = require("./core/config/config.service");
53
- const logging_service_1 = require("./core/logging/logging.service");
54
65
  const event_bus_service_1 = require("./core/events/event-bus.service");
55
- const storage_service_1 = require("./core/storage/storage.service");
56
- const capability_service_1 = require("./core/capability/capability.service");
57
66
  const feature_service_1 = require("./core/feature/feature.service");
58
- const auth_service_1 = require("./core/auth/auth.service");
59
- const stream_probe_service_1 = require("./core/streaming/stream-probe.service");
67
+ const lifecycle_runner_singleton_1 = require("./core/lifecycle/lifecycle-runner.singleton");
68
+ const logging_service_1 = require("./core/logging/logging.service");
69
+ const moleculer_service_1 = require("./core/moleculer/moleculer.service");
60
70
  const network_quality_service_1 = require("./core/network/network-quality.service");
61
- const toast_wrapper_service_1 = require("./core/notification/toast-wrapper.service");
62
71
  const notification_wrapper_service_1 = require("./core/notification/notification-wrapper.service");
63
- const addon_pages_service_1 = require("./core/addon-pages/addon-pages.service");
64
- const addon_widgets_service_1 = require("./core/addon-widgets/addon-widgets.service");
65
- const addon_bridge_service_1 = require("./core/addon-bridge/addon-bridge.service");
66
- const moleculer_service_1 = require("./core/moleculer/moleculer.service");
67
- const agent_registry_service_1 = require("./core/agent/agent-registry.service");
68
- const cluster_node_history_store_1 = require("./core/agent/cluster-node-history-store");
69
- const addon_upload_1 = require("./api/addon-upload");
70
- const addon_registry_service_1 = require("./core/addon/addon-registry.service");
71
- const addon_search_service_1 = require("./core/addon/addon-search.service");
72
- const addon_package_service_1 = require("./core/addon/addon-package.service");
72
+ const toast_wrapper_service_1 = require("./core/notification/toast-wrapper.service");
73
73
  const repl_engine_service_1 = require("./core/repl/repl-engine.service");
74
74
  const server_update_service_1 = require("./core/server-update/server-update.service");
75
+ const storage_service_1 = require("./core/storage/storage.service");
76
+ const stream_probe_service_1 = require("./core/streaming/stream-probe.service");
75
77
  const topology_emitter_service_1 = require("./core/topology/topology-emitter.service");
76
- const cap_providers_1 = require("./api/core/cap-providers");
77
- const post_boot_service_1 = require("./boot/post-boot.service");
78
78
  // ---------------------------------------------------------------------------
79
79
  // Service container — narrowing via `instanceof`, no casts.
80
80
  // ---------------------------------------------------------------------------
@@ -147,11 +147,30 @@ async function bootManual(opts) {
147
147
  const addonPackageService = new addon_package_service_1.AddonPackageService(loggingService, eventBusService, configService, addonRegistryService, notificationWrapper, toastWrapper);
148
148
  // Addon back-fill delivery seam. Wired here (not in the AgentRegistryService
149
149
  // constructor) because it needs AddonPackageService, which is a later layer.
150
- // Bytes come from the hub's own resolution of the package the agent needs no
151
- // registry reachability of its own, and every node in the cluster lands on the
152
- // same version. Delivery is an RPC pair, never an event (D8/D11).
150
+ // Bytes come from THE HUB'S OWN INSTALLED COPY whenever it has one, and only
151
+ // from the registry when it does not. This line used to claim that while
152
+ // calling `packPackage` `npm pack <name>@<version>` so the back-fill
153
+ // repaired a node by installing the REGISTRY's build of a version over the
154
+ // one an operator had just deployed. Same name, same version, different
155
+ // bundle: every `camstack deploy … -n little-unraid` on 2026-08-07 was
156
+ // reverted ~1.5s later and read as a stale dist. See `packInstalledPackage`.
157
+ // Delivery is an RPC pair, never an event (D8/D11).
158
+ const backfillLogger = loggingService.createLogger('agent-backfill');
153
159
  agentRegistryService.setAddonBackfill({
154
- pack: async (name, version) => (await addonPackageService.packPackage(name, version)).buffer,
160
+ pack: async (name, version) => {
161
+ const installed = await addonPackageService.packInstalledPackage(name);
162
+ // Name the SOURCE. This delivery overwrites whatever the node has, so
163
+ // "which build did we just push" is the first question every time it goes
164
+ // wrong — and for a whole day it was answered nowhere at all.
165
+ backfillLogger.info('back-fill packing', {
166
+ meta: {
167
+ packageName: name,
168
+ source: installed ? 'hub-installed' : 'npm-registry',
169
+ version: installed?.version ?? version,
170
+ },
171
+ });
172
+ return (installed ?? (await addonPackageService.packPackage(name, version))).buffer;
173
+ },
155
174
  deploy: async (nodeId, packageName, bundle) => {
156
175
  await moleculerService.broker.call('$agent.deploy', { addonId: packageName, source: (0, addon_upload_1.buildHubHttpSource)(bundle, (0, addon_upload_1.hubBundleBaseUrl)()) }, { nodeID: nodeId, timeout: agent_registry_service_1.AGENT_BACKFILL_RPC_TIMEOUT_MS });
157
176
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.70",
3
+ "version": "1.2.72",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.31",
36
+ "@camstack/addon-admin-ui": "1.2.33",
37
37
  "@camstack/addon-agent-ui": "1.2.10",
38
38
  "@camstack/addon-auth": "1.2.11",
39
39
  "@camstack/addon-decoder-nodeav": "1.2.9",
40
40
  "@camstack/addon-notifiers": "1.2.13",
41
- "@camstack/addon-pipeline": "1.2.42",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.25",
43
- "@camstack/addon-post-analysis": "1.2.47",
41
+ "@camstack/addon-pipeline": "1.2.44",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.27",
43
+ "@camstack/addon-post-analysis": "1.2.49",
44
44
  "@camstack/sdk": "1.2.10",
45
45
  "@camstack/shm-ring": "1.1.9",
46
- "@camstack/system": "1.2.57",
47
- "@camstack/types": "1.2.41",
48
- "@camstack/ui-library": "1.2.29",
46
+ "@camstack/system": "1.2.59",
47
+ "@camstack/types": "1.2.43",
48
+ "@camstack/ui-library": "1.2.31",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",