@camstack/server 1.2.30 → 1.2.33
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/agent/main.js +7 -0
- package/dist/api/trpc/core-cap-bridge.js +0 -1
- package/dist/api/trpc/generated-cap-mounts.js +18 -1
- package/dist/api/trpc/generated-cap-routers.js +1370 -1267
- package/dist/api/trpc/trpc.router.js +0 -2
- package/dist/core/addon/addon-registry.service.js +15 -0
- package/dist/core/addon/prune-misplaced-addons.js +138 -0
- package/dist/main.js +12 -4
- package/dist/server-root/index.js +101 -1
- package/package.json +14 -14
- package/dist/api/core/settings-backend.router.js +0 -121
|
@@ -17,7 +17,6 @@ const logs_router_js_1 = require("../core/logs.router.js");
|
|
|
17
17
|
const notifications_router_js_1 = require("../core/notifications.router.js");
|
|
18
18
|
const repl_router_js_1 = require("../core/repl.router.js");
|
|
19
19
|
const server_management_provider_js_1 = require("../core/server-management.provider.js");
|
|
20
|
-
const settings_backend_router_js_1 = require("../core/settings-backend.router.js");
|
|
21
20
|
const stream_probe_router_js_1 = require("../core/stream-probe.router.js");
|
|
22
21
|
const system_events_router_js_1 = require("../core/system-events.router.js");
|
|
23
22
|
const cap_router_runtime_js_1 = require("./cap-router-runtime.js");
|
|
@@ -260,7 +259,6 @@ function buildCapabilityRouters(services) {
|
|
|
260
259
|
// NOT the three-level settings gateway — that's the
|
|
261
260
|
// `addonSettings` cap router (mounted dynamically above).
|
|
262
261
|
addonSettingsRaw: (0, addon_settings_router_js_1.createAddonSettingsRouter)(services.configService),
|
|
263
|
-
settingsBackend: (0, settings_backend_router_js_1.createSettingsBackendRouter)(() => services.addonRegistry.getSettingsBackend()),
|
|
264
262
|
eventBusProxy: (0, event_bus_proxy_router_js_1.createEventBusProxyRouter)(services.eventBus),
|
|
265
263
|
repl: (0, repl_router_js_1.createReplRouter)(services.replEngine),
|
|
266
264
|
systemEvents: (0, system_events_router_js_1.createSystemEventsRouter)(services.eventBus),
|
|
@@ -42,6 +42,7 @@ const addon_row_manifest_1 = require("./addon-row-manifest");
|
|
|
42
42
|
const runner_convergence_1 = require("./runner-convergence");
|
|
43
43
|
const package_dir_utils_1 = require("./package-dir-utils");
|
|
44
44
|
const types_1 = require("@camstack/types");
|
|
45
|
+
const prune_misplaced_addons_js_1 = require("./prune-misplaced-addons.js");
|
|
45
46
|
const system_1 = require("@camstack/system");
|
|
46
47
|
const system_2 = require("@camstack/system");
|
|
47
48
|
const client_1 = require("@trpc/client");
|
|
@@ -481,6 +482,20 @@ class AddonRegistryService {
|
|
|
481
482
|
this.healthMonitor.recordFailure(fail.packageName, fail.error, fail.addonId);
|
|
482
483
|
}
|
|
483
484
|
const loadedAddons = this.addonLoader.listAddons();
|
|
485
|
+
// Self-heal a leftover from before the installer honoured placement:
|
|
486
|
+
// `@camstack/addon-agent-ui` sat in /data/addons on the live hub dated
|
|
487
|
+
// 2026-07-22, skipped by the loop below ever since, and therefore shown as
|
|
488
|
+
// FAILED forever on a node where it cannot run. Fixing the installer was
|
|
489
|
+
// necessary and not sufficient — nothing removed what was already there,
|
|
490
|
+
// and a permanent red entry teaches people to ignore red entries.
|
|
491
|
+
(0, prune_misplaced_addons_js_1.prunePlacementLeftovers)((0, prune_misplaced_addons_js_1.decidePrune)(loadedAddons.map((a) => ({
|
|
492
|
+
packageName: a.packageName,
|
|
493
|
+
dir: path.join(addonsDir, a.packageName),
|
|
494
|
+
placement: (0, types_1.resolveAddonPlacement)(a.declaration),
|
|
495
|
+
})), 'hub'), path.join(addonsDir, '@camstack'), {
|
|
496
|
+
info: (msg, meta) => this.logger.info(msg, { meta }),
|
|
497
|
+
warn: (msg, meta) => this.logger.warn(msg, { meta }),
|
|
498
|
+
});
|
|
484
499
|
for (const registered of loadedAddons) {
|
|
485
500
|
// Skip agent-only addons — they never run on the hub, only on remote
|
|
486
501
|
// agents that opt in via `execution.placement: 'agent-only'`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.decidePrune = decidePrune;
|
|
37
|
+
exports.isPrunableDir = isPrunableDir;
|
|
38
|
+
exports.prunePlacementLeftovers = prunePlacementLeftovers;
|
|
39
|
+
/**
|
|
40
|
+
* prune-misplaced-addons — remove an installed addon that can never run here.
|
|
41
|
+
*
|
|
42
|
+
* ## The state this exists to clear
|
|
43
|
+
*
|
|
44
|
+
* `@camstack/addon-agent-ui` declares `execution.placement: 'agent-only'`, and
|
|
45
|
+
* both hub-side load paths honour that: `addon-registry.service.ts` skips it at
|
|
46
|
+
* boot and after an install. `selectHubBootstrapPackages` also subtracts it, so
|
|
47
|
+
* a hub does not install it any more.
|
|
48
|
+
*
|
|
49
|
+
* None of that removes the copy an OLDER build already put on disk. On the live
|
|
50
|
+
* hub `/data/addons/@camstack/addon-agent-ui` is dated 2026-07-22 — eleven days
|
|
51
|
+
* before this was looked at. The loader skips it, so it never initialises, so
|
|
52
|
+
* the fleet UI shows it as **failed**, forever, on a node where it is not
|
|
53
|
+
* supposed to exist. An operator cannot tell that apart from an addon that is
|
|
54
|
+
* genuinely broken, which is the real cost: a permanent red entry teaches
|
|
55
|
+
* people to ignore red entries.
|
|
56
|
+
*
|
|
57
|
+
* Fixing the installer was necessary and not sufficient — the previous fix
|
|
58
|
+
* assumed no leftovers. This closes it.
|
|
59
|
+
*
|
|
60
|
+
* ## Why deleting is the right move
|
|
61
|
+
*
|
|
62
|
+
* An `agent-only` addon on a hub has no dormant use: it cannot be enabled, it
|
|
63
|
+
* cannot be reached, and keeping it costs a false failure. The directory is
|
|
64
|
+
* reinstallable from npm at any time, so removal is not destructive in the way
|
|
65
|
+
* deleting DATA would be. Anything outside `<dataDir>/addons/@camstack/` is
|
|
66
|
+
* refused outright.
|
|
67
|
+
*/
|
|
68
|
+
const fs = __importStar(require("node:fs"));
|
|
69
|
+
const path = __importStar(require("node:path"));
|
|
70
|
+
/**
|
|
71
|
+
* Decide what to prune for a node in `role`. Pure — the decision is the part
|
|
72
|
+
* worth testing, and it must never depend on a filesystem to be checked.
|
|
73
|
+
*
|
|
74
|
+
* Only the unambiguous direction is pruned: an `agent-only` addon on a hub. The
|
|
75
|
+
* mirror case (`hub-only` on an agent) is deliberately NOT handled here — an
|
|
76
|
+
* agent seeds its addon set from the hub's closure and a `hub-only` entry there
|
|
77
|
+
* is expected to be inert, so pruning it would fight the seeding rather than
|
|
78
|
+
* fix anything.
|
|
79
|
+
*/
|
|
80
|
+
function decidePrune(installed, role) {
|
|
81
|
+
if (role !== 'hub')
|
|
82
|
+
return [];
|
|
83
|
+
return installed
|
|
84
|
+
.filter((a) => a.placement === 'agent-only')
|
|
85
|
+
.map((a) => ({
|
|
86
|
+
packageName: a.packageName,
|
|
87
|
+
dir: a.dir,
|
|
88
|
+
reason: 'agent-only addon installed on the hub — it can never load here',
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/** Guard: only ever remove something under `<dataDir>/addons/@camstack/`. */
|
|
92
|
+
function isPrunableDir(dir, addonsRoot) {
|
|
93
|
+
const rel = path.relative(addonsRoot, dir);
|
|
94
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
|
|
95
|
+
return false;
|
|
96
|
+
// Exactly one level below the scope directory — never a nested path.
|
|
97
|
+
return rel.split(path.sep).filter(Boolean).length === 1;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Apply the decisions. Returns what was actually removed.
|
|
101
|
+
*
|
|
102
|
+
* A failure to remove one entry never aborts the rest and never throws into
|
|
103
|
+
* boot: the worst case is the pre-existing state — a stale entry showing as
|
|
104
|
+
* failed — which is exactly what we are trying to improve, not a reason to
|
|
105
|
+
* refuse to start.
|
|
106
|
+
*/
|
|
107
|
+
function prunePlacementLeftovers(decisions, addonsRoot, logger, rm = (dir) => fs.rmSync(dir, { recursive: true, force: true })) {
|
|
108
|
+
const removed = [];
|
|
109
|
+
for (const d of decisions) {
|
|
110
|
+
if (!isPrunableDir(d.dir, addonsRoot)) {
|
|
111
|
+
logger.warn('refusing to prune a path outside the addon scope', {
|
|
112
|
+
packageName: d.packageName,
|
|
113
|
+
dir: d.dir,
|
|
114
|
+
addonsRoot,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
rm(d.dir);
|
|
120
|
+
removed.push(d.packageName);
|
|
121
|
+
// Say it out loud: a directory disappearing at boot must never be
|
|
122
|
+
// something the operator discovers by noticing it is gone.
|
|
123
|
+
logger.info('pruned a misplaced addon', {
|
|
124
|
+
packageName: d.packageName,
|
|
125
|
+
dir: d.dir,
|
|
126
|
+
reason: d.reason,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
logger.warn('could not prune a misplaced addon', {
|
|
131
|
+
packageName: d.packageName,
|
|
132
|
+
dir: d.dir,
|
|
133
|
+
error: err instanceof Error ? err.message : String(err),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return removed;
|
|
138
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -82,11 +82,12 @@ const trpc_context_1 = require("./api/trpc/trpc.context");
|
|
|
82
82
|
const addon_upload_1 = require("./api/addon-upload");
|
|
83
83
|
const server_upload_1 = require("./api/server-upload");
|
|
84
84
|
const auth_whoami_1 = require("./api/auth-whoami");
|
|
85
|
+
const system_2 = require("@camstack/system");
|
|
85
86
|
const session_cookie_js_1 = require("./auth/session-cookie.js");
|
|
86
87
|
const health_routes_1 = require("./api/health/health.routes");
|
|
87
88
|
const spa_static_1 = require("./api/static/spa-static");
|
|
88
89
|
const oauth2_routes_js_1 = require("./api/oauth2/oauth2-routes.js");
|
|
89
|
-
const
|
|
90
|
+
const system_3 = require("@camstack/system");
|
|
90
91
|
const boot_config_1 = require("./boot/boot-config");
|
|
91
92
|
const manual_boot_1 = require("./manual-boot");
|
|
92
93
|
const post_boot_service_1 = require("./boot/post-boot.service");
|
|
@@ -178,6 +179,13 @@ function cleanupOrphanProcesses() {
|
|
|
178
179
|
}
|
|
179
180
|
// ---- Bootstrap ----
|
|
180
181
|
async function bootstrap() {
|
|
182
|
+
// hub-main's own memory, on the record. Deliberately started HERE and not in
|
|
183
|
+
// the starter: the starter runs from the image's /opt closure, which only
|
|
184
|
+
// changes with a new image, while this entry is what `applyServerUpdate`
|
|
185
|
+
// replaces — so the telemetry reaches a running hub the same day it is
|
|
186
|
+
// written. See packages/system/src/kernel/heap-watch.ts for why it exists
|
|
187
|
+
// (four silent OOMs in ~15h, one nine seconds after a viewer connected).
|
|
188
|
+
(0, system_2.startHeapWatch)();
|
|
181
189
|
// Clean up orphaned processes from previous crashes before starting
|
|
182
190
|
cleanupOrphanProcesses();
|
|
183
191
|
// SPA fallback — set later when admin UI is resolved, used by addon route catch-all
|
|
@@ -255,8 +263,8 @@ async function bootstrap() {
|
|
|
255
263
|
// If registration fails, start in degraded mode: serve a health warning endpoint.
|
|
256
264
|
// Instantiate new core services
|
|
257
265
|
const loggingService = app.get(logging_service_1.LoggingService);
|
|
258
|
-
const addonRouteRegistry = new
|
|
259
|
-
const dataPlaneRegistry = new
|
|
266
|
+
const addonRouteRegistry = new system_3.AddonRouteRegistry();
|
|
267
|
+
const dataPlaneRegistry = new system_3.DataPlaneRegistry();
|
|
260
268
|
// Use Fastify-managed notification/toast wrappers (globally provided by NotificationModule)
|
|
261
269
|
const { NotificationServiceWrapper } = await Promise.resolve().then(() => __importStar(require('./core/notification/notification-wrapper.service')));
|
|
262
270
|
const { ToastServiceWrapper } = await Promise.resolve().then(() => __importStar(require('./core/notification/toast-wrapper.service')));
|
|
@@ -785,7 +793,7 @@ async function bootstrap() {
|
|
|
785
793
|
const upstreamPath = `/${subPath}${queryString}`;
|
|
786
794
|
// Take over the socket — `proxyToUpstream` drives the raw response.
|
|
787
795
|
reply.hijack();
|
|
788
|
-
(0,
|
|
796
|
+
(0, system_3.proxyToUpstream)({
|
|
789
797
|
baseUrl: dpMatch.endpoint.baseUrl,
|
|
790
798
|
secret: dpMatch.endpoint.secret,
|
|
791
799
|
upstreamPath,
|
|
@@ -86,6 +86,7 @@ var require_dist = __commonJS({
|
|
|
86
86
|
SERVER_ROOT_DIRNAME: /* @__PURE__ */ __name(() => SERVER_ROOT_DIRNAME, "SERVER_ROOT_DIRNAME"),
|
|
87
87
|
SERVER_ROOT_STATE_FILE: /* @__PURE__ */ __name(() => SERVER_ROOT_STATE_FILE, "SERVER_ROOT_STATE_FILE"),
|
|
88
88
|
applyPendingRootSwap: /* @__PURE__ */ __name(() => applyPendingRootSwap, "applyPendingRootSwap"),
|
|
89
|
+
assessImageContract: /* @__PURE__ */ __name(() => assessImageContract, "assessImageContract"),
|
|
89
90
|
clearPendingRootSwap: /* @__PURE__ */ __name(() => clearPendingRootSwap, "clearPendingRootSwap"),
|
|
90
91
|
clearRestartIntentMarker: /* @__PURE__ */ __name(() => clearRestartIntentMarker, "clearRestartIntentMarker"),
|
|
91
92
|
compareSemver: /* @__PURE__ */ __name(() => compareSemver, "compareSemver"),
|
|
@@ -712,6 +713,82 @@ var require_dist = __commonJS({
|
|
|
712
713
|
};
|
|
713
714
|
}
|
|
714
715
|
__name(planBoot, "planBoot");
|
|
716
|
+
function releaseSeries(version) {
|
|
717
|
+
const [major, minor] = version.split(".").map((seg) => {
|
|
718
|
+
const n = Number.parseInt(seg, 10);
|
|
719
|
+
return Number.isNaN(n) ? 0 : n;
|
|
720
|
+
});
|
|
721
|
+
return [
|
|
722
|
+
major ?? 0,
|
|
723
|
+
minor ?? 0
|
|
724
|
+
];
|
|
725
|
+
}
|
|
726
|
+
__name(releaseSeries, "releaseSeries");
|
|
727
|
+
function sameSeries(a, b) {
|
|
728
|
+
const [aMajor, aMinor] = releaseSeries(a);
|
|
729
|
+
const [bMajor, bMinor] = releaseSeries(b);
|
|
730
|
+
return aMajor === bMajor && aMinor === bMinor;
|
|
731
|
+
}
|
|
732
|
+
__name(sameSeries, "sameSeries");
|
|
733
|
+
function behindMessage(seed, contract, series) {
|
|
734
|
+
const base = `This node is running an image whose baked seed is @camstack/server@${seed}; the deployment contract currently delivers ${contract}. applyServerUpdate keeps the code current but NEVER updates the image \u2014 the starter, the entrypoint and the boot fallback are still the old copies.`;
|
|
735
|
+
if (!series) {
|
|
736
|
+
return `${base} Recreate the container from the contract image to converge.`;
|
|
737
|
+
}
|
|
738
|
+
return `${base} The seed is a full release series behind \u2014 the shape of a pinned tag or an image repository that no longer receives builds, where a docker pull fixes nothing. Verify the container's image reference against the deployment templates.`;
|
|
739
|
+
}
|
|
740
|
+
__name(behindMessage, "behindMessage");
|
|
741
|
+
function assessImageContract(inputs) {
|
|
742
|
+
const { seedVersion, latestVersion, runningVersion } = inputs;
|
|
743
|
+
if (seedVersion === null) {
|
|
744
|
+
return {
|
|
745
|
+
state: "unknown",
|
|
746
|
+
seedVersion: null,
|
|
747
|
+
contractVersion: latestVersion,
|
|
748
|
+
contractSource: latestVersion !== null ? "registry" : null,
|
|
749
|
+
message: "No baked seed closure detected (dev workspace) \u2014 no image contract to compare."
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const contractVersion = latestVersion ?? (runningVersion !== null && compareSemver(seedVersion, runningVersion) < 0 ? runningVersion : null);
|
|
753
|
+
if (contractVersion === null) {
|
|
754
|
+
return {
|
|
755
|
+
state: "unknown",
|
|
756
|
+
seedVersion,
|
|
757
|
+
contractVersion: null,
|
|
758
|
+
contractSource: null,
|
|
759
|
+
message: `This node is running an image whose baked seed is @camstack/server@${seedVersion}; no registry check has run yet, so the release the contract image delivers is unknown. Run a server-update check to verify the image against the contract.`
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const contractSource = latestVersion !== null ? "registry" : "running";
|
|
763
|
+
const cmp = compareSemver(seedVersion, contractVersion);
|
|
764
|
+
if (cmp === 0) {
|
|
765
|
+
return {
|
|
766
|
+
state: "in-sync",
|
|
767
|
+
seedVersion,
|
|
768
|
+
contractVersion,
|
|
769
|
+
contractSource,
|
|
770
|
+
message: `Image seed @camstack/server@${seedVersion} matches the current release \u2014 this node is running the image the deployment contract names.`
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (cmp > 0) {
|
|
774
|
+
return {
|
|
775
|
+
state: "ahead",
|
|
776
|
+
seedVersion,
|
|
777
|
+
contractVersion,
|
|
778
|
+
contractSource,
|
|
779
|
+
message: `Image seed @camstack/server@${seedVersion} is NEWER than the best-known release ${contractVersion} \u2014 the registry check is stale or the registry was unreachable. Re-run a server-update check.`
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
const series = !sameSeries(seedVersion, contractVersion);
|
|
783
|
+
return {
|
|
784
|
+
state: series ? "behind-series" : "behind-patch",
|
|
785
|
+
seedVersion,
|
|
786
|
+
contractVersion,
|
|
787
|
+
contractSource,
|
|
788
|
+
message: behindMessage(seedVersion, contractVersion, series)
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
__name(assessImageContract, "assessImageContract");
|
|
715
792
|
var fs4 = __toESM2(require("fs"));
|
|
716
793
|
var path4 = __toESM2(require("path"));
|
|
717
794
|
function detectWorkspaceRoot(fromDir) {
|
|
@@ -955,7 +1032,16 @@ var require_dist = __commonJS({
|
|
|
955
1032
|
pendingVersion: pending?.version ?? null,
|
|
956
1033
|
rolledBack: null,
|
|
957
1034
|
stateFileCorrupt: false,
|
|
958
|
-
lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null
|
|
1035
|
+
lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null,
|
|
1036
|
+
// Seed-vs-contract verdict: `runningVersion` can be perfectly current
|
|
1037
|
+
// (applyServerUpdate) while the IMAGE is weeks old from a dead repo —
|
|
1038
|
+
// 2026-08-02, hub on `camstack-server:intel-1.1.74` with every other
|
|
1039
|
+
// field green. See `image-contract.ts` for the full account.
|
|
1040
|
+
imageContract: assessImageContract({
|
|
1041
|
+
seedVersion: this.seedVersion(),
|
|
1042
|
+
latestVersion,
|
|
1043
|
+
runningVersion
|
|
1044
|
+
})
|
|
959
1045
|
};
|
|
960
1046
|
}
|
|
961
1047
|
// ── Check ─────────────────────────────────────────────────────────────
|
|
@@ -989,6 +1075,20 @@ var require_dist = __commonJS({
|
|
|
989
1075
|
checkedAtMs: this.now(),
|
|
990
1076
|
error: null
|
|
991
1077
|
};
|
|
1078
|
+
const imageContract = assessImageContract({
|
|
1079
|
+
seedVersion: this.seedVersion(),
|
|
1080
|
+
latestVersion,
|
|
1081
|
+
runningVersion
|
|
1082
|
+
});
|
|
1083
|
+
if (imageContract.state === "behind-patch" || imageContract.state === "behind-series") {
|
|
1084
|
+
this.logger.warn(`image seed diverges from the release contract: ${imageContract.message}`, {
|
|
1085
|
+
meta: {
|
|
1086
|
+
state: imageContract.state,
|
|
1087
|
+
seedVersion: imageContract.seedVersion,
|
|
1088
|
+
contractVersion: imageContract.contractVersion
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
992
1092
|
const updateAvailable = latestVersion !== null && runningVersion !== null && compareSemver(latestVersion, runningVersion) > 0;
|
|
993
1093
|
return {
|
|
994
1094
|
packageName: this.spec.packageName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.33",
|
|
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.
|
|
37
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
38
|
-
"@camstack/addon-auth": "1.2.
|
|
39
|
-
"@camstack/addon-decoder-nodeav": "1.2.
|
|
40
|
-
"@camstack/addon-notifiers": "1.2.
|
|
41
|
-
"@camstack/addon-pipeline": "1.2.
|
|
42
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
43
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
44
|
-
"@camstack/sdk": "1.2.
|
|
45
|
-
"@camstack/shm-ring": "1.1.
|
|
46
|
-
"@camstack/system": "1.2.
|
|
47
|
-
"@camstack/types": "1.2.
|
|
48
|
-
"@camstack/ui-library": "1.2.
|
|
36
|
+
"@camstack/addon-admin-ui": "1.2.22",
|
|
37
|
+
"@camstack/addon-agent-ui": "1.2.10",
|
|
38
|
+
"@camstack/addon-auth": "1.2.10",
|
|
39
|
+
"@camstack/addon-decoder-nodeav": "1.2.9",
|
|
40
|
+
"@camstack/addon-notifiers": "1.2.11",
|
|
41
|
+
"@camstack/addon-pipeline": "1.2.39",
|
|
42
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.19",
|
|
43
|
+
"@camstack/addon-post-analysis": "1.2.23",
|
|
44
|
+
"@camstack/sdk": "1.2.9",
|
|
45
|
+
"@camstack/shm-ring": "1.1.9",
|
|
46
|
+
"@camstack/system": "1.2.29",
|
|
47
|
+
"@camstack/types": "1.2.21",
|
|
48
|
+
"@camstack/ui-library": "1.2.17",
|
|
49
49
|
"@fastify/compress": "^9.0.0",
|
|
50
50
|
"@fastify/cookie": "^11.0.2",
|
|
51
51
|
"@fastify/cors": "^11.2.0",
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSettingsBackendRouter = createSettingsBackendRouter;
|
|
4
|
-
/**
|
|
5
|
-
* Settings backend router — tRPC proxy for ISettingsBackend operations.
|
|
6
|
-
*
|
|
7
|
-
* Exposes the core collection-based operations (get, set, query, insert,
|
|
8
|
-
* update, delete, count, isEmpty) so forked worker addons can use
|
|
9
|
-
* context.settingsBackend via tRPC instead of requiring in-process access
|
|
10
|
-
* to the SQLite database.
|
|
11
|
-
*
|
|
12
|
-
* Introduced for Task 11 — TrpcSettingsBackend for forked workers.
|
|
13
|
-
*/
|
|
14
|
-
const zod_1 = require("zod");
|
|
15
|
-
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Zod schemas
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
const CollectionKeySchema = zod_1.z.object({
|
|
20
|
-
collection: zod_1.z.string(),
|
|
21
|
-
key: zod_1.z.string(),
|
|
22
|
-
});
|
|
23
|
-
const SetValueSchema = zod_1.z.object({
|
|
24
|
-
collection: zod_1.z.string(),
|
|
25
|
-
key: zod_1.z.string(),
|
|
26
|
-
value: zod_1.z.unknown(),
|
|
27
|
-
});
|
|
28
|
-
const QueryFilterSchema = zod_1.z
|
|
29
|
-
.object({
|
|
30
|
-
where: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
31
|
-
whereIn: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.unknown())).optional(),
|
|
32
|
-
whereBetween: zod_1.z.record(zod_1.z.string(), zod_1.z.tuple([zod_1.z.unknown(), zod_1.z.unknown()])).optional(),
|
|
33
|
-
orderBy: zod_1.z
|
|
34
|
-
.object({
|
|
35
|
-
field: zod_1.z.string(),
|
|
36
|
-
direction: zod_1.z.enum(['asc', 'desc']),
|
|
37
|
-
})
|
|
38
|
-
.optional(),
|
|
39
|
-
limit: zod_1.z.number().optional(),
|
|
40
|
-
offset: zod_1.z.number().optional(),
|
|
41
|
-
})
|
|
42
|
-
.optional();
|
|
43
|
-
const QueryInputSchema = zod_1.z.object({
|
|
44
|
-
collection: zod_1.z.string(),
|
|
45
|
-
filter: QueryFilterSchema,
|
|
46
|
-
});
|
|
47
|
-
const InsertInputSchema = zod_1.z.object({
|
|
48
|
-
collection: zod_1.z.string(),
|
|
49
|
-
record: zod_1.z.object({
|
|
50
|
-
id: zod_1.z.string(),
|
|
51
|
-
data: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
52
|
-
}),
|
|
53
|
-
});
|
|
54
|
-
const UpdateInputSchema = zod_1.z.object({
|
|
55
|
-
collection: zod_1.z.string(),
|
|
56
|
-
id: zod_1.z.string(),
|
|
57
|
-
data: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
58
|
-
});
|
|
59
|
-
const CountInputSchema = zod_1.z.object({
|
|
60
|
-
collection: zod_1.z.string(),
|
|
61
|
-
filter: QueryFilterSchema,
|
|
62
|
-
});
|
|
63
|
-
const IsEmptyInputSchema = zod_1.z.object({
|
|
64
|
-
collection: zod_1.z.string(),
|
|
65
|
-
});
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Router factory
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
function createSettingsBackendRouter(getBackend) {
|
|
70
|
-
const requireBackend = () => {
|
|
71
|
-
const backend = getBackend();
|
|
72
|
-
if (!backend) {
|
|
73
|
-
throw new Error('Settings backend not available — settings-store addon may not be initialized yet');
|
|
74
|
-
}
|
|
75
|
-
return backend;
|
|
76
|
-
};
|
|
77
|
-
return (0, trpc_middleware_js_1.trpcRouter)({
|
|
78
|
-
get: trpc_middleware_js_1.protectedProcedure.input(CollectionKeySchema).query(async ({ input }) => {
|
|
79
|
-
const result = await requireBackend().get(input);
|
|
80
|
-
return { value: result };
|
|
81
|
-
}),
|
|
82
|
-
set: trpc_middleware_js_1.protectedProcedure.input(SetValueSchema).mutation(async ({ input }) => {
|
|
83
|
-
await requireBackend().set({
|
|
84
|
-
collection: input.collection,
|
|
85
|
-
key: input.key,
|
|
86
|
-
value: input.value,
|
|
87
|
-
});
|
|
88
|
-
return { success: true };
|
|
89
|
-
}),
|
|
90
|
-
query: trpc_middleware_js_1.protectedProcedure.input(QueryInputSchema).query(async ({ input }) => {
|
|
91
|
-
const records = await requireBackend().query({
|
|
92
|
-
collection: input.collection,
|
|
93
|
-
filter: input.filter ?? undefined,
|
|
94
|
-
});
|
|
95
|
-
return { records: records.map((r) => ({ id: r.id, data: r.data })) };
|
|
96
|
-
}),
|
|
97
|
-
insert: trpc_middleware_js_1.protectedProcedure.input(InsertInputSchema).mutation(async ({ input }) => {
|
|
98
|
-
await requireBackend().insert(input);
|
|
99
|
-
return { success: true };
|
|
100
|
-
}),
|
|
101
|
-
update: trpc_middleware_js_1.protectedProcedure.input(UpdateInputSchema).mutation(async ({ input }) => {
|
|
102
|
-
await requireBackend().update(input);
|
|
103
|
-
return { success: true };
|
|
104
|
-
}),
|
|
105
|
-
delete: trpc_middleware_js_1.protectedProcedure.input(CollectionKeySchema).mutation(async ({ input }) => {
|
|
106
|
-
await requireBackend().delete(input);
|
|
107
|
-
return { success: true };
|
|
108
|
-
}),
|
|
109
|
-
count: trpc_middleware_js_1.protectedProcedure.input(CountInputSchema).query(async ({ input }) => {
|
|
110
|
-
const result = await requireBackend().count({
|
|
111
|
-
collection: input.collection,
|
|
112
|
-
filter: input.filter ?? undefined,
|
|
113
|
-
});
|
|
114
|
-
return { count: result };
|
|
115
|
-
}),
|
|
116
|
-
isEmpty: trpc_middleware_js_1.protectedProcedure.input(IsEmptyInputSchema).query(async ({ input }) => {
|
|
117
|
-
const result = await requireBackend().isEmpty(input);
|
|
118
|
-
return { empty: result };
|
|
119
|
-
}),
|
|
120
|
-
});
|
|
121
|
-
}
|