@camstack/system 1.2.122 → 1.2.124

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.
Files changed (59) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-bindings-store.d.ts +20 -0
  16. package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
  17. package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
  18. package/dist/builtins/device-manager/device-manager.addon.js +73 -7
  19. package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  22. package/dist/builtins/hub-forwarder/index.js +1 -1
  23. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  28. package/dist/builtins/local-network/local-network.addon.js +1 -1
  29. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  30. package/dist/builtins/loki-logging/index.js +1 -1
  31. package/dist/builtins/loki-logging/index.mjs +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  34. package/dist/builtins/platform-probe/index.js +1 -1
  35. package/dist/builtins/platform-probe/index.mjs +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/snapshot/index.js +1 -1
  39. package/dist/builtins/snapshot/index.mjs +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
  51. package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
  52. package/dist/index.js +2 -2
  53. package/dist/index.mjs +2 -2
  54. package/dist/kernel/heap-watch.d.ts +80 -1
  55. package/dist/{manifest-python-deps-DZSNlHjU.mjs → manifest-python-deps-D1Ydw7J0.mjs} +93 -8
  56. package/dist/{manifest-python-deps-76dZLxD0.js → manifest-python-deps-DB_4H3tR.js} +93 -8
  57. package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
  58. package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
  59. package/package.json +1 -1
@@ -10,6 +10,7 @@ import * as os from "node:os";
10
10
  import { tmpdir } from "node:os";
11
11
  import * as fs from "node:fs";
12
12
  import { createServer } from "node:http";
13
+ import { monitorEventLoopDelay } from "node:perf_hooks";
13
14
  import * as v8 from "node:v8";
14
15
  import * as vm from "node:vm";
15
16
  import { BOOT_RECOVERY_BACKOFF_MS, DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, createEventBusSliceSource, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
@@ -166,6 +167,72 @@ var HEAP_RECLAIM_TRIGGER_MB = 1024;
166
167
  * router's time, against a ~1.5GiB peak it removes each pass.
167
168
  */
168
169
  var HEAP_RECLAIM_MIN_INTERVAL_MS = 12e4;
170
+ /** What a loop with nothing recorded yet reports. */
171
+ var ZERO_LOOP_DELAY = {
172
+ p50Ms: 0,
173
+ p99Ms: 0,
174
+ maxMs: 0
175
+ };
176
+ /**
177
+ * A `perf_hooks` interval histogram, read once per LOGGED line.
178
+ *
179
+ * The histogram samples in libuv at `resolutionMs` and costs nothing to keep
180
+ * enabled; the expensive part would be reading it on every 2s probe, which
181
+ * would also destroy the window the line claims to describe (probe and log
182
+ * cadences are deliberately different — see {@link startHeapWatch}).
183
+ *
184
+ * An EMPTY histogram is the trap: Node answers `min = 9223372036854776000` and
185
+ * `percentile()` throws on some versions, so a first heartbeat that read it
186
+ * naively would print `loopMax=9223372036854ms` and be read as a catastrophe on
187
+ * every boot. `count === 0` short-circuits to {@link ZERO_LOOP_DELAY}.
188
+ *
189
+ * Returns undefined if the platform declines, so the caller keeps a heartbeat
190
+ * either way.
191
+ */
192
+ function createLoopDelayMeter(resolutionMs = 20) {
193
+ try {
194
+ const histogram = monitorEventLoopDelay({ resolution: resolutionMs });
195
+ histogram.enable();
196
+ return {
197
+ read: () => {
198
+ try {
199
+ if (histogram.count === 0) return ZERO_LOOP_DELAY;
200
+ const sample = {
201
+ p50Ms: Math.round(histogram.percentile(50) / 1e6),
202
+ p99Ms: Math.round(histogram.percentile(99) / 1e6),
203
+ maxMs: Math.round(histogram.max / 1e6)
204
+ };
205
+ histogram.reset();
206
+ return sample;
207
+ } catch {
208
+ return ZERO_LOOP_DELAY;
209
+ }
210
+ },
211
+ stop: () => {
212
+ histogram.disable();
213
+ }
214
+ };
215
+ } catch {
216
+ return;
217
+ }
218
+ }
219
+ /**
220
+ * Read the origin of the old-space ceiling off node's own options.
221
+ *
222
+ * hub-main is the only process in the cluster whose ceiling nobody chose. Every
223
+ * forked runner is given V8 heap flags by `process-service.ts`; hub-main gets
224
+ * `--max-old-space-size` from `docker-entrypoint.sh` ONLY when the operator sets
225
+ * `CAMSTACK_HUB_HEAP_MB`, and on the live container it is unset — verified
226
+ * 2026-08-25 by reading pid 7's argv, a bare `node .../starter.js`. So the
227
+ * ceiling in force is whatever V8 derives from the host (4288MB there), and the
228
+ * only record of that was a comment inside an image nobody rebuilds to read.
229
+ *
230
+ * A fact about the running process belongs in the running process's log, and
231
+ * `execArgv` answers it for free at boot.
232
+ */
233
+ function heapCeilingOrigin(execArgv = process.execArgv) {
234
+ return execArgv.some((arg) => /^--max[-_]old[-_]space[-_]size(=|$)/.test(arg)) ? "explicit" : "v8-default";
235
+ }
169
236
  var MB = (bytes) => Math.round(bytes / 1048576);
170
237
  /**
171
238
  * Build a sample from raw readings. Pure so the thresholds are testable without
@@ -229,8 +296,10 @@ function createV8Reclaimer() {
229
296
  return;
230
297
  }
231
298
  }
232
- function format(label, s) {
233
- return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
299
+ function format(label, s, loop) {
300
+ const line = `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
301
+ if (loop === void 0) return line;
302
+ return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms`;
234
303
  }
235
304
  /**
236
305
  * Start the heartbeat. Returns a stop function.
@@ -238,7 +307,7 @@ function format(label, s) {
238
307
  * `unref`ed so it can never hold the process open, and every read is wrapped:
239
308
  * a diagnostic that can crash the process it watches is worse than none.
240
309
  */
241
- function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
310
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
242
311
  const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
243
312
  const now = reclaimOptions?.now ?? (() => Date.now());
244
313
  const triggerMb = reclaimOptions?.triggerMb ?? 1024;
@@ -247,14 +316,24 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
247
316
  const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
248
317
  const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
249
318
  let lastReclaimAt = Number.NEGATIVE_INFINITY;
319
+ /** Consecutive passes that fired the instant the floor elapsed. See
320
+ * {@link RECLAIM_STEADY_STATE_PASSES}. */
321
+ let passesAtFloor = 0;
322
+ let steadyStateAnnounced = false;
250
323
  const read = () => {
251
324
  const limit = reclaimOptions?.heapLimitBytes ?? v8.getHeapStatistics().heap_size_limit;
252
325
  return buildHeapSample(readMemory(), limit);
253
326
  };
254
327
  const maybeReclaim = (sample) => {
255
328
  if (reclaimOptions === void 0) return;
256
- if (!shouldReclaim(sample, triggerMb)) return;
257
- if (now() - lastReclaimAt < minIntervalMs) return;
329
+ if (!shouldReclaim(sample, triggerMb)) {
330
+ passesAtFloor = 0;
331
+ steadyStateAnnounced = false;
332
+ return;
333
+ }
334
+ const sinceLast = now() - lastReclaimAt;
335
+ if (sinceLast < minIntervalMs) return;
336
+ passesAtFloor = sinceLast <= minIntervalMs * 2 ? passesAtFloor + 1 : 1;
258
337
  lastReclaimAt = now();
259
338
  const startedAt = now();
260
339
  try {
@@ -265,6 +344,10 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
265
344
  }
266
345
  const after = read();
267
346
  sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
347
+ if (passesAtFloor >= 6 && !steadyStateAnnounced) {
348
+ steadyStateAnnounced = true;
349
+ sink.warn(`[mem] reclaim ${label} has run at the floor for ${passesAtFloor} consecutive passes (every ${minIntervalMs}ms) — the stop-the-world is now STEADY STATE, not a rescue. The allocation source is what needs the fix; this pass only bounds its peak.`);
350
+ }
268
351
  };
269
352
  let mode = "steady";
270
353
  let lastLoggedAt = Number.NEGATIVE_INFINITY;
@@ -293,7 +376,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
293
376
  const due = at - lastLoggedAt >= intervalMs;
294
377
  if (mode === "escalated" || due) {
295
378
  lastLoggedAt = at;
296
- const line = format(label, sample);
379
+ const line = format(label, sample, loopDelay?.read());
297
380
  if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
298
381
  else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
299
382
  else sink.info(line);
@@ -305,11 +388,13 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
305
388
  const timer = setInterval(tick, probeIntervalMs);
306
389
  timer.unref?.();
307
390
  tick();
391
+ if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
308
392
  let stopped = false;
309
393
  return () => {
310
394
  if (stopped) return;
311
395
  stopped = true;
312
396
  clearInterval(timer);
397
+ loopDelay?.stop();
313
398
  };
314
399
  }
315
400
  /**
@@ -358,13 +443,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
358
443
  function startRunnerHeapWatch(options) {
359
444
  if (options.heapProfile !== "heavy") return void 0;
360
445
  const intervalMs = options.intervalMs ?? 3e5;
361
- if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
446
+ if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
362
447
  let reclaimOptions = options.reclaimOptions;
363
448
  if (reclaimOptions === void 0) {
364
449
  const reclaimer = createV8Reclaimer();
365
450
  reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
366
451
  }
367
- return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
452
+ return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, false);
368
453
  }
369
454
  //#endregion
370
455
  //#region src/kernel/moleculer/addon-data-plane-facility.ts
@@ -12,6 +12,7 @@ node_os = require_chunk.__toESM(node_os);
12
12
  let node_fs = require("node:fs");
13
13
  node_fs = require_chunk.__toESM(node_fs);
14
14
  let node_http = require("node:http");
15
+ let node_perf_hooks = require("node:perf_hooks");
15
16
  let node_v8 = require("node:v8");
16
17
  node_v8 = require_chunk.__toESM(node_v8);
17
18
  let node_vm = require("node:vm");
@@ -170,6 +171,72 @@ var HEAP_RECLAIM_TRIGGER_MB = 1024;
170
171
  * router's time, against a ~1.5GiB peak it removes each pass.
171
172
  */
172
173
  var HEAP_RECLAIM_MIN_INTERVAL_MS = 12e4;
174
+ /** What a loop with nothing recorded yet reports. */
175
+ var ZERO_LOOP_DELAY = {
176
+ p50Ms: 0,
177
+ p99Ms: 0,
178
+ maxMs: 0
179
+ };
180
+ /**
181
+ * A `perf_hooks` interval histogram, read once per LOGGED line.
182
+ *
183
+ * The histogram samples in libuv at `resolutionMs` and costs nothing to keep
184
+ * enabled; the expensive part would be reading it on every 2s probe, which
185
+ * would also destroy the window the line claims to describe (probe and log
186
+ * cadences are deliberately different — see {@link startHeapWatch}).
187
+ *
188
+ * An EMPTY histogram is the trap: Node answers `min = 9223372036854776000` and
189
+ * `percentile()` throws on some versions, so a first heartbeat that read it
190
+ * naively would print `loopMax=9223372036854ms` and be read as a catastrophe on
191
+ * every boot. `count === 0` short-circuits to {@link ZERO_LOOP_DELAY}.
192
+ *
193
+ * Returns undefined if the platform declines, so the caller keeps a heartbeat
194
+ * either way.
195
+ */
196
+ function createLoopDelayMeter(resolutionMs = 20) {
197
+ try {
198
+ const histogram = (0, node_perf_hooks.monitorEventLoopDelay)({ resolution: resolutionMs });
199
+ histogram.enable();
200
+ return {
201
+ read: () => {
202
+ try {
203
+ if (histogram.count === 0) return ZERO_LOOP_DELAY;
204
+ const sample = {
205
+ p50Ms: Math.round(histogram.percentile(50) / 1e6),
206
+ p99Ms: Math.round(histogram.percentile(99) / 1e6),
207
+ maxMs: Math.round(histogram.max / 1e6)
208
+ };
209
+ histogram.reset();
210
+ return sample;
211
+ } catch {
212
+ return ZERO_LOOP_DELAY;
213
+ }
214
+ },
215
+ stop: () => {
216
+ histogram.disable();
217
+ }
218
+ };
219
+ } catch {
220
+ return;
221
+ }
222
+ }
223
+ /**
224
+ * Read the origin of the old-space ceiling off node's own options.
225
+ *
226
+ * hub-main is the only process in the cluster whose ceiling nobody chose. Every
227
+ * forked runner is given V8 heap flags by `process-service.ts`; hub-main gets
228
+ * `--max-old-space-size` from `docker-entrypoint.sh` ONLY when the operator sets
229
+ * `CAMSTACK_HUB_HEAP_MB`, and on the live container it is unset — verified
230
+ * 2026-08-25 by reading pid 7's argv, a bare `node .../starter.js`. So the
231
+ * ceiling in force is whatever V8 derives from the host (4288MB there), and the
232
+ * only record of that was a comment inside an image nobody rebuilds to read.
233
+ *
234
+ * A fact about the running process belongs in the running process's log, and
235
+ * `execArgv` answers it for free at boot.
236
+ */
237
+ function heapCeilingOrigin(execArgv = process.execArgv) {
238
+ return execArgv.some((arg) => /^--max[-_]old[-_]space[-_]size(=|$)/.test(arg)) ? "explicit" : "v8-default";
239
+ }
173
240
  var MB = (bytes) => Math.round(bytes / 1048576);
174
241
  /**
175
242
  * Build a sample from raw readings. Pure so the thresholds are testable without
@@ -233,8 +300,10 @@ function createV8Reclaimer() {
233
300
  return;
234
301
  }
235
302
  }
236
- function format(label, s) {
237
- return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
303
+ function format(label, s, loop) {
304
+ const line = `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
305
+ if (loop === void 0) return line;
306
+ return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms`;
238
307
  }
239
308
  /**
240
309
  * Start the heartbeat. Returns a stop function.
@@ -242,7 +311,7 @@ function format(label, s) {
242
311
  * `unref`ed so it can never hold the process open, and every read is wrapped:
243
312
  * a diagnostic that can crash the process it watches is worse than none.
244
313
  */
245
- function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
314
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
246
315
  const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
247
316
  const now = reclaimOptions?.now ?? (() => Date.now());
248
317
  const triggerMb = reclaimOptions?.triggerMb ?? 1024;
@@ -251,14 +320,24 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
251
320
  const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
252
321
  const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
253
322
  let lastReclaimAt = Number.NEGATIVE_INFINITY;
323
+ /** Consecutive passes that fired the instant the floor elapsed. See
324
+ * {@link RECLAIM_STEADY_STATE_PASSES}. */
325
+ let passesAtFloor = 0;
326
+ let steadyStateAnnounced = false;
254
327
  const read = () => {
255
328
  const limit = reclaimOptions?.heapLimitBytes ?? node_v8.getHeapStatistics().heap_size_limit;
256
329
  return buildHeapSample(readMemory(), limit);
257
330
  };
258
331
  const maybeReclaim = (sample) => {
259
332
  if (reclaimOptions === void 0) return;
260
- if (!shouldReclaim(sample, triggerMb)) return;
261
- if (now() - lastReclaimAt < minIntervalMs) return;
333
+ if (!shouldReclaim(sample, triggerMb)) {
334
+ passesAtFloor = 0;
335
+ steadyStateAnnounced = false;
336
+ return;
337
+ }
338
+ const sinceLast = now() - lastReclaimAt;
339
+ if (sinceLast < minIntervalMs) return;
340
+ passesAtFloor = sinceLast <= minIntervalMs * 2 ? passesAtFloor + 1 : 1;
262
341
  lastReclaimAt = now();
263
342
  const startedAt = now();
264
343
  try {
@@ -269,6 +348,10 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
269
348
  }
270
349
  const after = read();
271
350
  sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
351
+ if (passesAtFloor >= 6 && !steadyStateAnnounced) {
352
+ steadyStateAnnounced = true;
353
+ sink.warn(`[mem] reclaim ${label} has run at the floor for ${passesAtFloor} consecutive passes (every ${minIntervalMs}ms) — the stop-the-world is now STEADY STATE, not a rescue. The allocation source is what needs the fix; this pass only bounds its peak.`);
354
+ }
272
355
  };
273
356
  let mode = "steady";
274
357
  let lastLoggedAt = Number.NEGATIVE_INFINITY;
@@ -297,7 +380,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
297
380
  const due = at - lastLoggedAt >= intervalMs;
298
381
  if (mode === "escalated" || due) {
299
382
  lastLoggedAt = at;
300
- const line = format(label, sample);
383
+ const line = format(label, sample, loopDelay?.read());
301
384
  if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
302
385
  else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
303
386
  else sink.info(line);
@@ -309,11 +392,13 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
309
392
  const timer = setInterval(tick, probeIntervalMs);
310
393
  timer.unref?.();
311
394
  tick();
395
+ if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
312
396
  let stopped = false;
313
397
  return () => {
314
398
  if (stopped) return;
315
399
  stopped = true;
316
400
  clearInterval(timer);
401
+ loopDelay?.stop();
317
402
  };
318
403
  }
319
404
  /**
@@ -362,13 +447,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
362
447
  function startRunnerHeapWatch(options) {
363
448
  if (options.heapProfile !== "heavy") return void 0;
364
449
  const intervalMs = options.intervalMs ?? 3e5;
365
- if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
450
+ if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
366
451
  let reclaimOptions = options.reclaimOptions;
367
452
  if (reclaimOptions === void 0) {
368
453
  const reclaimer = createV8Reclaimer();
369
454
  reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
370
455
  }
371
- return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
456
+ return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, false);
372
457
  }
373
458
  //#endregion
374
459
  //#region src/kernel/moleculer/addon-data-plane-facility.ts
@@ -1,4 +1,4 @@
1
- import { kt as asJsonObject } from "./dist-CEhrHp3u.mjs";
1
+ import { kt as asJsonObject } from "./dist-CvJrtLkw.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DnhGRFEn.js");
1
+ const require_dist = require("./dist-CIDgcxPk.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.122",
3
+ "version": "1.2.124",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",