@camstack/server 1.2.458 → 1.2.460

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.
@@ -134,7 +134,7 @@ entry kept resolvable for a saved selection but no longer offered.
134
134
 
135
135
  | Catalog ids | Upstream | Code / weights | Training data | CamStack changes | Downloaded from |
136
136
  | --- | --- | --- | --- | --- | --- |
137
- | `yunet-2023mar` (default) | [OpenCV Zoo YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet) | BSD-3-Clause (`BSD-3-Clause.txt`) / MIT (`MIT-yunet-reference.txt`) | WIDER FACE: research-oriented terms; images CC BY-NC-ND | input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py) | mirror `faceDetection/yunet` |
137
+ | `yunet-2023mar` (default) | [OpenCV Zoo YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet) | BSD-3-Clause (`BSD-3-Clause.txt`) / MIT (`MIT-yunet-reference.txt`) | WIDER FACE: research-oriented terms; images CC BY-NC-ND | input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py); Edge TPU build: the bake folded into the first Conv, full-integer int8 post-training quantisation calibrated on CamStack person crops, compiled with edgetpu_compiler 14 (scripts/build-yunet-edgetpu.py) | mirror `faceDetection/yunet` |
138
138
 
139
139
  ### Face recognition
140
140
 
@@ -146,7 +146,7 @@ entry kept resolvable for a saved selection but no longer offered.
146
146
 
147
147
  | Catalog ids | Upstream | Code / weights | Training data | CamStack changes | Downloaded from |
148
148
  | --- | --- | --- | --- | --- | --- |
149
- | `yolox-tiny-plate` (default) | [open-lpr models (faisalthaheem/open-lpr-models), YOLOX-tiny](https://huggingface.co/faisalthaheem/open-lpr-models) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | Simanno annotated plate corpus plus community-contributed photographs, pseudo-labelled by a teacher model (not published; the upstream NOTICE says the photographs were uploaded without consent obtained): UNKNOWN | input contract baked into the graph (static 1x3x640x640 RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather) and the output reshaped to the YOLOv8 layout (cx, cy, w, h, obj*cls); ONNX fp32, OpenVINO IR fp16, CoreML fp16 (scripts/build-yolox-plate-model.py) | mirror `plateDetection/yolox-tiny-plate` |
149
+ | `yolox-tiny-plate` (default) | [open-lpr models (faisalthaheem/open-lpr-models), YOLOX-tiny](https://huggingface.co/faisalthaheem/open-lpr-models) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | Simanno annotated plate corpus plus community-contributed photographs, pseudo-labelled by a teacher model (not published; the upstream NOTICE says the photographs were uploaded without consent obtained): UNKNOWN | input contract baked into the graph (static 1x3x640x640 RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather) and the output reshaped to the YOLOv8 layout (cx, cy, w, h, obj*cls); ONNX fp32, OpenVINO IR fp16, CoreML fp16 (scripts/build-yolox-plate-model.py); Edge TPU build: the Focus stem rewritten as the equivalent 6x6 stride-2 Conv with the input bake folded into it, the graph cut before the grid decode (raw per-stride head), full-integer int8 post-training quantisation calibrated on CamStack vehicle crops, compiled with edgetpu_compiler 14 (scripts/build-yolox-plate-edgetpu.py) | mirror `plateDetection/yolox-tiny-plate` |
150
150
 
151
151
  ### Licence plate reading
152
152
 
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.AgentUpdateService = exports.AGENT_RESTART_GRACE_MS = exports.AGENT_RUNTIME_ADDON_ID = void 0;
36
+ exports.AgentUpdateService = exports.AgentRestartAnnouncer = exports.AGENT_RESTART_GRACE_MS = exports.AGENT_RUNTIME_ADDON_ID = void 0;
37
37
  exports.isRunningInContainer = isRunningInContainer;
38
38
  exports.resolveAgentPackageJsonPath = resolveAgentPackageJsonPath;
39
39
  exports.scheduleAgentRestart = scheduleAgentRestart;
@@ -149,7 +149,61 @@ function scheduleAgentRestart(logger, requestedBy, dataDir) {
149
149
  }, exports.AGENT_RESTART_GRACE_MS);
150
150
  grace.unref();
151
151
  }
152
+ /**
153
+ * The agent's restart ANNOUNCEMENT (D723): `system.restarting`, emitted before
154
+ * the self-SIGTERM, through the agent's event bus — which broadcasts it to the
155
+ * hub, where the notification centre turns it into `node-restarting` or
156
+ * `node-updating`. Until D723 the agent emitted nothing before exiting, so an
157
+ * agent's restart was announced only after the fact (`system.restart-completed`).
158
+ *
159
+ * Its own small object because the restart seam is a closure handed to the base
160
+ * class BEFORE `this` exists, while the bus is bound later (`bindAvailability`,
161
+ * once the mesh is up). A restart before that has nobody to tell — said, never
162
+ * silent.
163
+ */
164
+ class AgentRestartAnnouncer {
165
+ logger;
166
+ eventBus;
167
+ nodeId;
168
+ constructor(logger, eventBus, nodeId) {
169
+ this.logger = logger;
170
+ this.eventBus = eventBus;
171
+ this.nodeId = nodeId;
172
+ }
173
+ bind(eventBus, nodeId) {
174
+ this.eventBus = eventBus;
175
+ this.nodeId = nodeId;
176
+ }
177
+ announce(marker) {
178
+ const bus = this.eventBus;
179
+ if (bus === null) {
180
+ this.logger.warn('agent restart NOT announced — the event bus is not bound yet', {
181
+ meta: { nodeId: this.nodeId, kind: marker.kind },
182
+ });
183
+ return;
184
+ }
185
+ const payload = { ...marker, nodeId: this.nodeId };
186
+ try {
187
+ bus.emit({
188
+ id: (0, node_crypto_1.randomUUID)(),
189
+ timestamp: new Date(),
190
+ source: { type: 'core', id: 'agent-update-service' },
191
+ category: types_1.EventCategory.SystemRestarting,
192
+ data: payload,
193
+ });
194
+ }
195
+ catch (err) {
196
+ // Telemetry (D8): an announcement that could not go out must never block
197
+ // the restart it announces.
198
+ this.logger.warn('agent restart announcement failed', {
199
+ meta: { nodeId: this.nodeId, error: err instanceof Error ? err.message : String(err) },
200
+ });
201
+ }
202
+ }
203
+ }
204
+ exports.AgentRestartAnnouncer = AgentRestartAnnouncer;
152
205
  class AgentUpdateService extends index_js_1.RootUpdateService {
206
+ restartAnnouncer;
153
207
  updateAvailability;
154
208
  eventBus;
155
209
  nodeId;
@@ -164,6 +218,7 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
164
218
  // handshake settles the id; the log tag keeps the boot-time value, which is
165
219
  // the same string in every deployment that sets `CAMSTACK_NODE_ID`.
166
220
  const nodeId = options.nodeId ?? process.env['CAMSTACK_NODE_ID'] ?? 'agent';
221
+ const restartAnnouncer = new AgentRestartAnnouncer(options.logger, options.eventBus ?? null, nodeId);
167
222
  super({
168
223
  spec: index_js_1.HUB_ROOT_SPEC,
169
224
  nodeId,
@@ -177,7 +232,8 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
177
232
  },
178
233
  logger: options.logger,
179
234
  restartServer: (requestedBy, update) => {
180
- writeAgentRestartMarker(options.dataDir, requestedBy, update);
235
+ const marker = writeAgentRestartMarker(options.dataDir, requestedBy, update);
236
+ restartAnnouncer.announce(marker);
181
237
  restartAgent(requestedBy);
182
238
  },
183
239
  dataDir: options.dataDir,
@@ -204,6 +260,7 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
204
260
  now: options.now,
205
261
  });
206
262
  this.nodeId = nodeId;
263
+ this.restartAnnouncer = restartAnnouncer;
207
264
  this.agentDataDir = options.dataDir;
208
265
  this.agentLogger = options.logger;
209
266
  this.eventBus = options.eventBus ?? null;
@@ -220,6 +277,7 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
220
277
  bindAvailability(eventBus, nodeId) {
221
278
  this.eventBus = eventBus;
222
279
  this.nodeId = nodeId;
280
+ this.restartAnnouncer.bind(eventBus, nodeId);
223
281
  this.updateAvailability = new update_availability_emitter_js_1.UpdateAvailabilityEmitter(eventBus, { type: 'core', id: 'agent-update-service' }, new update_availability_store_js_1.FileUpdateAvailabilityStore(this.agentDataDir, 'agent-update', this.agentLogger));
224
282
  }
225
283
  async checkServerUpdate() {
@@ -266,19 +324,22 @@ class AgentUpdateService extends index_js_1.RootUpdateService {
266
324
  }
267
325
  }
268
326
  exports.AgentUpdateService = AgentUpdateService;
327
+ /** Write the marker and return it — the same payload is the announcement (D723). */
269
328
  function writeAgentRestartMarker(dataDir, requestedBy, update) {
329
+ // Same builder as the hub: an agent's "Server aggiornato" notification must
330
+ // name both versions too — see `core/updates/restart-marker.ts`.
331
+ const marker = (0, restart_marker_js_1.buildRestartMarker)({
332
+ requestedAt: Date.now(),
333
+ requestedBy,
334
+ ...(update !== undefined ? { update } : {}),
335
+ });
270
336
  try {
271
- // Same builder as the hub: an agent's "Server aggiornato" notification must
272
- // name both versions too — see `core/updates/restart-marker.ts`.
273
- (0, system_1.writePendingRestart)(dataDir, (0, restart_marker_js_1.buildRestartMarker)({
274
- requestedAt: Date.now(),
275
- requestedBy,
276
- ...(update !== undefined ? { update } : {}),
277
- }));
337
+ (0, system_1.writePendingRestart)(dataDir, marker);
278
338
  }
279
339
  catch {
280
340
  // Marker write must never block the restart.
281
341
  }
342
+ return marker;
282
343
  }
283
344
  /**
284
345
  * The manifest entry for the agent runtime's own providers. Appended to
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.ignoredTlsCertSources = ignoredTlsCertSources;
41
42
  exports.loadBootstrapConfig = loadBootstrapConfig;
42
43
  exports.autoGenerateJwtSecret = autoGenerateJwtSecret;
43
44
  exports.setupInfra = setupInfra;
@@ -67,9 +68,15 @@ const ENV_VAR_MAP = {
67
68
  CAMSTACK_ADMIN_USER: 'auth.adminUsername',
68
69
  CAMSTACK_ADMIN_PASS: 'auth.adminPassword',
69
70
  CAMSTACK_TLS_ENABLED: 'tls.enabled',
70
- CAMSTACK_TLS_CERT: 'tls.certPath',
71
- CAMSTACK_TLS_KEY: 'tls.keyPath',
72
71
  };
72
+ /**
73
+ * Env variables that once set the node's certificate. Ignored since D726:
74
+ * the admin UI (Network → Local access) is the one way to set it. Still
75
+ * named here so a node that has them set is told, not silently re-keyed.
76
+ */
77
+ const IGNORED_TLS_CERT_ENV = ['CAMSTACK_TLS_CERT', 'CAMSTACK_TLS_KEY'];
78
+ /** The `config.yaml` keys those variables were mapped onto — ignored too. */
79
+ const IGNORED_TLS_CERT_KEYS = ['certPath', 'keyPath'];
73
80
  // ---------------------------------------------------------------------------
74
81
  // Helpers
75
82
  // ---------------------------------------------------------------------------
@@ -82,6 +89,20 @@ function setNested(obj, p, value) {
82
89
  const child = (0, types_1.asJsonObject)(obj[head]) ?? {};
83
90
  return { ...obj, [head]: setNested(child, rest.join('.'), value) };
84
91
  }
92
+ /**
93
+ * Every env variable and `config.yaml` key that would once have set the
94
+ * certificate and is now ignored (D726). Blank env values count as unset,
95
+ * exactly as the override loop treats them.
96
+ */
97
+ function ignoredTlsCertSources(env, raw) {
98
+ const fromEnv = IGNORED_TLS_CERT_ENV.filter((name) => {
99
+ const value = env[name];
100
+ return value !== undefined && value !== '';
101
+ });
102
+ const tls = (0, types_1.asJsonObject)(raw['tls']) ?? {};
103
+ const fromYaml = IGNORED_TLS_CERT_KEYS.filter((key) => tls[key] !== undefined).map((key) => `tls.${key}`);
104
+ return [...fromEnv, ...fromYaml];
105
+ }
85
106
  // ---------------------------------------------------------------------------
86
107
  // loadBootstrapConfig
87
108
  // ---------------------------------------------------------------------------
@@ -145,6 +166,11 @@ function loadBootstrapConfig(configPath) {
145
166
  raw = setNested(raw, configPath_, coerced);
146
167
  console.log(`[Phase1] Env override: ${envKey} → ${configPath_}`);
147
168
  }
169
+ const ignored = ignoredTlsCertSources(process.env, raw);
170
+ if (ignored.length > 0) {
171
+ console.warn(`[Phase1] WARN ${ignored.join(', ')} ignored — the certificate is set only in the admin UI. ` +
172
+ 'Upload the certificate in Network → Local access (D726).');
173
+ }
148
174
  return config_schema_1.bootstrapSchema.parse(raw);
149
175
  }
150
176
  // ---------------------------------------------------------------------------
@@ -210,22 +236,21 @@ async function setupInfra(configPath, bootstrapConfig) {
210
236
  // broken chunks with require("fs") when leaked .js files exist in core/src/.
211
237
  // CJS build works correctly and tsx supports require().
212
238
  const core = require('@camstack/system');
213
- const { ensureTlsCert, loadTlsCert } = core;
214
- if (config.tls.certPath && config.tls.keyPath) {
215
- // User-provided cert
216
- console.log(`[Phase2c] Loading custom TLS cert from ${config.tls.certPath}`);
217
- const pair = loadTlsCert(config.tls.certPath, config.tls.keyPath);
218
- tlsOptions = { key: pair.key, cert: pair.cert };
239
+ // The certificate comes only from the admin UI (D726): the generated
240
+ // local CA → leaf (D227), or an operator upload served verbatim.
241
+ const extraSans = [...core.readExtraSans(dataPath)];
242
+ const boot = await core.prepareBootTls(dataPath, extraSans.length > 0 ? { extraSans } : undefined);
243
+ if (boot.uploadedMissing) {
244
+ console.warn(`[Phase2c] WARN the uploaded TLS certificate is missing from ${path.join(dataPath, 'tls')} — serving the generated one. Upload it again in Network → Local access.`);
245
+ }
246
+ if (boot.ensured !== undefined) {
247
+ tlsCert = boot.ensured;
248
+ logTlsCert(boot.ensured);
219
249
  }
220
250
  else {
221
- // Hub-generated local CA → leaf (D227)
222
- const extraSans = [...(core.readExtraSans?.(dataPath) ?? [])];
223
- const tlsResult = await ensureTlsCert(dataPath, extraSans.length > 0 ? { extraSans } : undefined);
224
- tlsCert = tlsResult;
225
- logTlsCert(tlsResult);
226
- const pair = loadTlsCert(tlsResult.certPath, tlsResult.keyPath);
227
- tlsOptions = { key: pair.key, cert: pair.cert };
251
+ console.log(`[Phase2c] Serving the uploaded TLS certificate from ${path.join(dataPath, 'tls')}`);
228
252
  }
253
+ tlsOptions = { key: boot.pair.key, cert: boot.pair.cert };
229
254
  }
230
255
  return {
231
256
  bootstrapConfig: config,
@@ -1146,7 +1146,11 @@ class AddonPackageService {
1146
1146
  timestamp: new Date(),
1147
1147
  source: { type: 'core', id: 'addon-package-service' },
1148
1148
  category: types_1.EventCategory.SystemRestarting,
1149
- data: payload,
1149
+ // The node, as `system.restart-completed` names it after the boot
1150
+ // (`post-boot.service.ts`): the announcement and the landing of one
1151
+ // restart must name the same node, or a stale `node-updating` could not
1152
+ // be matched to the `server-updated` that supersedes it (D723).
1153
+ data: { ...payload, nodeId: process.env['CAMSTACK_NODE_ID'] ?? 'hub' },
1150
1154
  });
1151
1155
  // 10s grace gives in-flight requests time to drain.
1152
1156
  (0, system_1.scheduleSelfRestart)({ delayMs: 10_000 });
package/dist/main.js CHANGED
@@ -40,6 +40,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const node_child_process_1 = require("node:child_process");
41
41
  const fs = __importStar(require("node:fs"));
42
42
  const path = __importStar(require("node:path"));
43
+ const node_tls_1 = require("node:tls");
43
44
  // Boot-time capability declaration runs over the auto-generated
44
45
  // `ALL_CAPABILITY_DEFINITIONS` array — every `*.cap.ts` file that ships
45
46
  // with `@camstack/types` is included automatically. Adding a new cap
@@ -1489,6 +1490,17 @@ async function bootstrap() {
1489
1490
  const logger = app.get(logging_service_1.LoggingService).createLogger('System');
1490
1491
  const protocol = tlsOptions ? 'https' : 'http';
1491
1492
  logger.info('CamStack server listening', { meta: { protocol, host, port, trpcRegistered } });
1493
+ // Certificate actions (Network → Local access) swap the served certificate
1494
+ // in place through this registration — without it an upload sat on disk
1495
+ // while the listener kept serving the boot-time leaf (D726).
1496
+ if (tlsOptions) {
1497
+ if (fastify.server instanceof node_tls_1.Server) {
1498
+ (0, system_1.registerHttpsListener)(fastify.server, tlsOptions);
1499
+ }
1500
+ else {
1501
+ logger.warn('HTTPS listener is not a TLS server — certificate changes need a restart');
1502
+ }
1503
+ }
1492
1504
  // LAN HTTP sibling — same Fastify request graph, dedicated port. Handler
1493
1505
  // is registered as soon as we have the instance; bind happens after HTTPS
1494
1506
  // is up so a failed HTTPS listen does not leave HTTP serving alone.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.458",
3
+ "version": "1.2.460",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -37,18 +37,18 @@
37
37
  ]
38
38
  },
39
39
  "dependencies": {
40
- "@camstack/addon-admin-ui": "1.2.332",
41
- "@camstack/addon-agent-ui": "1.2.175",
40
+ "@camstack/addon-admin-ui": "1.2.334",
41
+ "@camstack/addon-agent-ui": "1.2.176",
42
42
  "@camstack/addon-auth": "1.2.127",
43
43
  "@camstack/addon-decoder-nodeav": "1.2.119",
44
44
  "@camstack/addon-notifiers": "1.2.128",
45
- "@camstack/addon-pipeline": "1.2.328",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.283",
47
- "@camstack/addon-post-analysis": "1.2.334",
45
+ "@camstack/addon-pipeline": "1.2.329",
46
+ "@camstack/addon-pipeline-orchestrator": "1.2.284",
47
+ "@camstack/addon-post-analysis": "1.2.336",
48
48
  "@camstack/sdk": "1.2.122",
49
- "@camstack/system": "1.2.355",
50
- "@camstack/types": "1.2.306",
51
- "@camstack/ui-library": "1.2.233",
49
+ "@camstack/system": "1.2.357",
50
+ "@camstack/types": "1.2.308",
51
+ "@camstack/ui-library": "1.2.234",
52
52
  "@fastify/compress": "^9.0.0",
53
53
  "@fastify/cookie": "^11.0.2",
54
54
  "@fastify/cors": "^11.2.0",