@camstack/server 1.2.8 → 1.2.10

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.
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.AddonPackageService = exports.FRAMEWORK_PACKAGES = exports.SYSTEM_PACKAGE = void 0;
36
+ exports.AddonPackageService = exports.FRAMEWORK_PACKAGES = exports.AUTO_UPDATE_EXCLUDED_PACKAGES = exports.SYSTEM_PACKAGE = void 0;
37
+ exports.isVersionNewer = isVersionNewer;
37
38
  exports.isFrameworkPackage = isFrameworkPackage;
38
39
  exports.extractTgzStripped = extractTgzStripped;
39
40
  const fs = __importStar(require("node:fs"));
@@ -51,6 +52,53 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
51
52
  * export because `lifecycle-job-runner` + several tests reference it directly.
52
53
  */
53
54
  exports.SYSTEM_PACKAGE = '@camstack/system';
55
+ /**
56
+ * Packages the npm auto-updater must NEVER touch: the framework/system tier
57
+ * ships exclusively via `applyServerUpdate` (single-copy collapse). Listed
58
+ * explicitly (not via a manifest flag) because the updater runs against
59
+ * `listInstalled()` package names before any manifest is loaded.
60
+ */
61
+ exports.AUTO_UPDATE_EXCLUDED_PACKAGES = new Set([
62
+ exports.SYSTEM_PACKAGE,
63
+ '@camstack/types',
64
+ '@camstack/kernel',
65
+ '@camstack/core',
66
+ '@camstack/sdk',
67
+ '@camstack/ui-library',
68
+ '@camstack/server',
69
+ ]);
70
+ /**
71
+ * True when `target` is a STRICTLY newer semver than `current`. Minimal
72
+ * x.y.z(-prerelease) comparator — our registry versions are plain publishes,
73
+ * and a dedicated semver dependency isn't declared in this package. Numeric
74
+ * triples compare first; a prerelease ranks BELOW its release (1.2.6-dev <
75
+ * 1.2.6); when both carry prereleases they compare lexicographically.
76
+ * Unparseable versions are never "newer" (fail safe: no update).
77
+ */
78
+ function isVersionNewer(target, current) {
79
+ const parse = (v) => {
80
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(v.trim());
81
+ if (!m)
82
+ return null;
83
+ return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
84
+ };
85
+ const t = parse(target);
86
+ const c = parse(current);
87
+ if (!t || !c)
88
+ return false;
89
+ for (let i = 0; i < 3; i += 1) {
90
+ if (t.nums[i] !== c.nums[i])
91
+ return t.nums[i] > c.nums[i];
92
+ }
93
+ // Same triple: release > prerelease; two prereleases compare lexically.
94
+ if (t.pre === null && c.pre !== null)
95
+ return true;
96
+ if (t.pre !== null && c.pre === null)
97
+ return false;
98
+ if (t.pre !== null && c.pre !== null)
99
+ return t.pre > c.pre;
100
+ return false;
101
+ }
54
102
  /**
55
103
  * Host-provided framework packages that take a special path through
56
104
  * `camstack deploy` (the addon-upload route) instead of the normal
@@ -1145,6 +1193,13 @@ class AddonPackageService {
1145
1193
  const targets = [];
1146
1194
  for (const pkg of installed) {
1147
1195
  try {
1196
+ // Framework packages NEVER auto-update from npm — they ship via
1197
+ // `applyServerUpdate` (single-copy collapse). The engine already
1198
+ // skipped a swept framework target; excluding it here also kills the
1199
+ // useless candidate churn that participated in the 2026-07-22 hub
1200
+ // wedge (mass staged swap right after a publish train).
1201
+ if (exports.AUTO_UPDATE_EXCLUDED_PACKAGES.has(pkg.name))
1202
+ continue;
1148
1203
  // Determine effective channel for this addon
1149
1204
  const addonId = pkg.name.replace('@camstack/addon-', '').replace('@camstack/', '');
1150
1205
  const override = this.autoUpdateConfig.overrides[addonId];
@@ -1165,7 +1220,13 @@ class AddonPackageService {
1165
1220
  const targetVersion = effectiveChannel === 'beta'
1166
1221
  ? asString(distTags['beta']) || asString(distTags['latest'])
1167
1222
  : asString(distTags['latest']);
1168
- if (!targetVersion || targetVersion === pkg.version)
1223
+ // FORWARD-ONLY: update only when the registry advertises a STRICTLY
1224
+ // NEWER semver. The old `!==` comparison happily "updated" BACKWARD —
1225
+ // on 2026-07-22 it replaced same-day CLI-deployed builds (labelled
1226
+ // with the older workspace version) with the freshly published npm
1227
+ // artifacts, rolling back live fixes. A dev build whose label equals
1228
+ // or exceeds the npm tag is left alone.
1229
+ if (!targetVersion || !isVersionNewer(targetVersion, pkg.version))
1169
1230
  continue;
1170
1231
  this.logger.info('Auto-update candidate', {
1171
1232
  meta: {
@@ -1225,11 +1225,11 @@ class AddonRegistryService {
1225
1225
  if (initResult?.customActions && initResult.actionHandlers) {
1226
1226
  const handlers = initResult.actionHandlers;
1227
1227
  try {
1228
- this.customActionRegistry.registerAddon(id, initResult.customActions, (action, input) => {
1228
+ this.customActionRegistry.registerAddon(id, initResult.customActions, (action, input, caller) => {
1229
1229
  const fn = handlers[action];
1230
1230
  if (!fn)
1231
1231
  throw new Error(`addon '${id}' has no handler for custom action '${action}'`);
1232
- return fn(input);
1232
+ return fn(input, caller);
1233
1233
  });
1234
1234
  }
1235
1235
  catch (err) {
@@ -2987,7 +2987,7 @@ class AddonRegistryService {
2987
2987
  });
2988
2988
  return;
2989
2989
  }
2990
- this.customActionRegistry.registerAddon(addonId, catalog, (action, input) => this.dispatchForkedCustomAction(addonId, action, input));
2990
+ this.customActionRegistry.registerAddon(addonId, catalog, (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
2991
2991
  this.logger.info('Runner addon custom actions registered', {
2992
2992
  tags: { addonId },
2993
2993
  meta: { runnerId },
@@ -2999,11 +2999,15 @@ class AddonRegistryService {
2999
2999
  * routing + the child-availability error; there is no broker fallback after
3000
3000
  * the per-addon Moleculer broker was removed.
3001
3001
  */
3002
- async dispatchForkedCustomAction(addonId, action, input) {
3002
+ async dispatchForkedCustomAction(addonId, action, input, caller) {
3003
3003
  return this.addonCallGateway.callForked(addonId, {
3004
3004
  target: 'custom',
3005
3005
  action,
3006
3006
  args: input,
3007
+ // Additive: only carry `caller` when the hub actually forwarded one (the
3008
+ // router populates it exclusively for a `caller:'required'` action of an
3009
+ // authenticated request). Never blanket; never from client input.
3010
+ ...(caller ? { caller } : {}),
3007
3011
  });
3008
3012
  }
3009
3013
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,20 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.6",
37
- "@camstack/addon-advanced-notifier": "1.2.4",
36
+ "@camstack/addon-admin-ui": "1.2.7",
38
37
  "@camstack/addon-agent-ui": "1.2.4",
39
38
  "@camstack/addon-auth": "1.2.4",
40
39
  "@camstack/addon-decoder-nodeav": "1.2.4",
41
- "@camstack/addon-notifiers": "1.2.4",
42
- "@camstack/addon-pipeline": "1.2.6",
43
- "@camstack/addon-pipeline-orchestrator": "1.2.7",
44
- "@camstack/addon-post-analysis": "1.2.6",
40
+ "@camstack/addon-notifiers": "1.2.5",
41
+ "@camstack/addon-pipeline": "1.2.8",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.9",
43
+ "@camstack/addon-post-analysis": "1.2.8",
45
44
  "@camstack/sdk": "1.2.4",
46
45
  "@camstack/shm-ring": "1.1.4",
47
- "@camstack/system": "1.2.7",
48
- "@camstack/types": "1.2.7",
49
- "@camstack/ui-library": "1.2.5",
46
+ "@camstack/system": "1.2.9",
47
+ "@camstack/types": "1.2.9",
48
+ "@camstack/ui-library": "1.2.6",
50
49
  "@fastify/compress": "^9.0.0",
51
50
  "@fastify/cookie": "^11.0.2",
52
51
  "@fastify/cors": "^11.2.0",
@@ -1,91 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAddonsCustomProcedures = createAddonsCustomProcedures;
4
- /**
5
- * `api.addons.custom` — generic dispatcher for addon-defined custom actions.
6
- *
7
- * Task 7.2 of the device-proxy redesign. Addons declare a catalog of
8
- * custom actions at boot via `AddonInitResult.customActions` + a single
9
- * `handleCustomAction(action, input)` handler. The catalog is registered
10
- * with a per-process `CustomActionRegistry` (Task 7.1). This endpoint is
11
- * the single tRPC entry point that resolves an `(addonId, action)` pair,
12
- * validates input + output against the action's Zod schemas, enforces the
13
- * action's declared auth level, and dispatches to the addon handler.
14
- *
15
- * The factory returns a record of procedures (not a router) so the caller
16
- * can spread it into the existing `addons` namespace:
17
- *
18
- * trpcRouter({
19
- * ...existingAddonsProcedures,
20
- * ...createAddonsCustomProcedures({ getCustomActionRegistry: ... }),
21
- * })
22
- *
23
- * This avoids `mergeRouters` (which requires sharing the `t` instance
24
- * across modules) while still mounting the procedure at `api.addons.custom`.
25
- */
26
- const zod_1 = require("zod");
27
- const server_1 = require("@trpc/server");
28
- const trpc_middleware_js_1 = require("./trpc/trpc.middleware.js");
29
- /**
30
- * Build the procedure record for the `custom` endpoint.
31
- *
32
- * The OUTER procedure is `protectedProcedure` — every caller must be
33
- * authenticated. The INNER per-action auth declared in `spec.auth` is
34
- * enforced manually by `ensureAuth` because the auth level is not known
35
- * until after the registry lookup.
36
- */
37
- function createAddonsCustomProcedures(deps) {
38
- return {
39
- custom: trpc_middleware_js_1.protectedProcedure
40
- .input(zod_1.z.object({
41
- addonId: zod_1.z.string().min(1),
42
- action: zod_1.z.string().min(1),
43
- input: zod_1.z.unknown(),
44
- }))
45
- .output(zod_1.z.unknown())
46
- .mutation(async ({ input, ctx }) => {
47
- const registry = deps.getCustomActionRegistry();
48
- const entry = registry.resolve(input.addonId, input.action);
49
- if (!entry) {
50
- throw new server_1.TRPCError({
51
- code: 'NOT_FOUND',
52
- message: `addon '${input.addonId}' has no custom action '${input.action}'`,
53
- });
54
- }
55
- // Per-action authorization. The outer procedure already requires
56
- // authentication; here we additionally enforce the declared role
57
- // when it's stricter than 'protected'.
58
- ensureAuth(ctx, entry.spec.auth);
59
- // Validate input against the action's declared Zod schema.
60
- const parsedInput = entry.spec.input.parse(input.input);
61
- // Dispatch through the addon handler.
62
- const result = await entry.handler(parsedInput);
63
- // Validate the addon's output. Crash-early on misbehaving addons.
64
- return entry.spec.output.parse(result);
65
- }),
66
- };
67
- }
68
- /**
69
- * Enforce the action's declared auth level.
70
- *
71
- * Mirrors the role checks performed by `protectedProcedure` and
72
- * `adminProcedure` in trpc.middleware.ts:
73
- * - public: no auth
74
- * - protected: any authenticated user
75
- * - admin: isAdmin only (scoped tokens bounce)
76
- */
77
- function ensureAuth(ctx, level) {
78
- if (level === 'public')
79
- return;
80
- if (!ctx.user) {
81
- throw new server_1.TRPCError({ code: 'UNAUTHORIZED' });
82
- }
83
- if (level === 'protected')
84
- return;
85
- if (level === 'admin') {
86
- if (!ctx.user.isAdmin) {
87
- throw new server_1.TRPCError({ code: 'FORBIDDEN', message: 'custom action requires admin' });
88
- }
89
- return;
90
- }
91
- }