@camstack/system 1.2.123 → 1.2.125

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 (61) hide show
  1. package/dist/addon-runner.js +224 -224
  2. package/dist/addon-runner.mjs +224 -224
  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 +43 -4
  53. package/dist/index.mjs +43 -4
  54. package/dist/kernel/heap-watch.d.ts +12 -9
  55. package/dist/kernel/moleculer/process-service.d.ts +12 -1
  56. package/dist/kernel/moleculer/runner-native-allocator.d.ts +95 -0
  57. package/dist/{manifest-python-deps-B3_4YiDK.js → manifest-python-deps-BTkFwAk_.js} +15 -12
  58. package/dist/{manifest-python-deps-DIIXlR1x.mjs → manifest-python-deps-Dmcnb287.mjs} +15 -12
  59. package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
  60. package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Native-allocator environment for a forked addon runner.
3
+ *
4
+ * ## The half of a runner's memory that no V8 flag reaches
5
+ *
6
+ * `--max-old-space-size` bounds V8's old space and nothing else. It does not
7
+ * bound `external`, it does not bound `arrayBuffers`, and it does not bound what
8
+ * a native addon (`sharp`/`libvips`, decoders, codecs) allocates through `malloc`.
9
+ *
10
+ * Measured on the live hub, 2026-08-25, over a ~6h window of `[mem]` heartbeats:
11
+ *
12
+ * hub/pipeline-analytics rss 309-7334MB heapTotal max 343MB external max 5479MB
13
+ *
14
+ * It already carried `--max-old-space-size=1024`, so the flag was in force the
15
+ * whole time and the process still reached 7.3GB — because 96% of it was never
16
+ * V8's to bound. Its reclaim line names the shape exactly:
17
+ *
18
+ * reclaim hub/pipeline-analytics stranded=2552MB rss=2765MB→2647MB
19
+ * freed=118MB arrayBuffers=81MB→29MB took=58ms
20
+ *
21
+ * The `arrayBuffers` FLOOR returns to ~1MB every cycle, so this is not a leak.
22
+ * It is glibc holding freed pages: with `MALLOC_ARENA_MAX` unset, glibc opens up
23
+ * to `8 × ncpu` arenas — 160 on this 20-core host — and each keeps its own free
24
+ * list, which is returned to the OS only when the top of that arena is free.
25
+ *
26
+ * ## Why this matters more than the cgroup number suggests
27
+ *
28
+ * The container is capped at 24GiB and `memory.current` rides at 21-24GB. Near
29
+ * the limit the kernel performs direct reclaim IN THE CONTEXT OF THE TASK THAT
30
+ * ALLOCATES, so every process in the container stalls together — measured as
31
+ * four idle, mutually unrelated runners sharing one ~39s stall in the same
32
+ * sample. Returning stranded pages is therefore not a tidiness win; it is what
33
+ * keeps unrelated runners off the direct-reclaim path.
34
+ *
35
+ * ## Why `VIPS_CONCURRENCY` is welded to it
36
+ *
37
+ * `sharp` reads `MALLOC_ARENA_MAX` in its NATIVE binding (not in JS — grepping
38
+ * the bundle finds nothing). Unset on glibc, sharp pins libvips concurrency to 1
39
+ * as its own fragmentation defence; the moment the variable appears it restores
40
+ * concurrency to `availableParallelism()`. Verified in the live container:
41
+ *
42
+ * MALLOC_ARENA_MAX unset → sharp.concurrency() === 1
43
+ * MALLOC_ARENA_MAX=2 → sharp.concurrency() === 20
44
+ * MALLOC_ARENA_MAX=2 VIPS_CONCURRENCY=1 → sharp.concurrency() === 1
45
+ *
46
+ * Shipping the arena bound alone would therefore have quietly shipped a 1→20
47
+ * libvips threading change to the busiest addon on a host already at ~75% CPU,
48
+ * under the banner of a memory fix. This module ships the allocator change and
49
+ * ONLY the allocator change; raising libvips concurrency is a separate decision.
50
+ *
51
+ * ## What it costs, measured
52
+ *
53
+ * Same container, same 12-image corpus, same pipeline as analytics (decode →
54
+ * extract → resize → encode → raw), 3 interleaved pairs so host load cancels,
55
+ * concurrency pinned to 1 on both arms:
56
+ *
57
+ * arenas default : mean 51.9ms p50 60.0ms p90 67.3ms p99 70.7ms rss 505MB
58
+ * arenas=2 : mean 51.5ms p50 57.6ms p90 67.2ms p99 72.9ms rss 354MB
59
+ *
60
+ * Latency is unchanged inside noise (-0.8% mean, +3.1% p99); the same work is
61
+ * done in 30% less RSS.
62
+ */
63
+ /** Bounded glibc arenas per forked runner. */
64
+ export declare const RUNNER_MALLOC_ARENA_MAX_DEFAULT = 2;
65
+ /**
66
+ * libvips worker threads per forked runner. `1` is not a tuning choice — it is
67
+ * sharp's OWN default on glibc, pinned here so that bounding the arenas does not
68
+ * change it as a side effect. See the module header.
69
+ */
70
+ export declare const RUNNER_VIPS_CONCURRENCY_DEFAULT = 1;
71
+ /**
72
+ * The native-allocator variables to add to a runner's environment. Both keys are
73
+ * present or both are absent — see the `VIPS_CONCURRENCY` note in the module
74
+ * header for why one without the other is the dangerous configuration.
75
+ */
76
+ export interface RunnerNativeAllocatorEnv {
77
+ readonly MALLOC_ARENA_MAX?: string;
78
+ readonly VIPS_CONCURRENCY?: string;
79
+ }
80
+ /**
81
+ * Resolve the native-allocator env for a child runner from the parent's env.
82
+ *
83
+ * Precedence, highest first:
84
+ * 1. `CAMSTACK_RUNNER_NATIVE_ALLOCATOR=off` — kill switch, returns nothing and
85
+ * restores the glibc/sharp defaults. Recoverable without a rebuild.
86
+ * 2. A value the operator already put in the environment (`MALLOC_ARENA_MAX`,
87
+ * `VIPS_CONCURRENCY`) — inherited verbatim, never overwritten.
88
+ * 3. `CAMSTACK_RUNNER_MALLOC_ARENA_MAX` — the CamStack override.
89
+ * 4. The defaults above.
90
+ *
91
+ * A nonsense override falls back to the default rather than being passed through:
92
+ * glibc reads `MALLOC_ARENA_MAX=0` as "unbounded", which is the very state this
93
+ * exists to leave.
94
+ */
95
+ export declare function runnerNativeAllocatorEnv(parentEnv: NodeJS.ProcessEnv): RunnerNativeAllocatorEnv;
@@ -311,7 +311,7 @@ function format(label, s, loop) {
311
311
  * `unref`ed so it can never hold the process open, and every read is wrapped:
312
312
  * a diagnostic that can crash the process it watches is worse than none.
313
313
  */
314
- function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv) {
314
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
315
315
  const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
316
316
  const now = reclaimOptions?.now ?? (() => Date.now());
317
317
  const triggerMb = reclaimOptions?.triggerMb ?? 1024;
@@ -392,7 +392,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
392
392
  const timer = setInterval(tick, probeIntervalMs);
393
393
  timer.unref?.();
394
394
  tick();
395
- if (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.`);
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.`);
396
396
  let stopped = false;
397
397
  return () => {
398
398
  if (stopped) return;
@@ -422,14 +422,17 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
422
422
  *
423
423
  * ## Why heavy runners get hub-main's mechanism
424
424
  *
425
- * The media-path runners deliberately run with NO old-space ceiling
426
- * (`maxOldSpaceMb: 0` a flat cap turned a disk-stall backlog into
427
- * `Ineffective mark-compacts near heap limit` and killed recording, twice on
428
- * 2026-08-17). Uncapped, V8 feels no pressure, so a one-off burst becomes the
429
- * runner's permanent RSS: the recorder measured 2122MB holding a ~50MB index.
430
- * hub-main had exactly this shape and its reclaimer is proven live — 11 passes
431
- * in 4h, 375–1652MB returned per pass at 550–680ms each. Same mechanism, same
432
- * `shouldReclaim` stranded-over-trigger gate; never a second implementation.
425
+ * Because a heap ceiling cannot reach what actually strands in them. Every
426
+ * media-path runner now carries an old-space ceiling sized from its own
427
+ * measured peak (`addon-pipeline` manifest + its `manifest-heap-ceilings` spec),
428
+ * and that changes nothing here: what a heavy runner strands is NATIVE —
429
+ * `external`, `arrayBuffers`, glibc arena free lists none of which
430
+ * `--max-old-space-size` bounds. Measured 2026-08-25, `hub/pipeline-analytics`
431
+ * reported `stranded=2552MB` while holding an 81MB V8 heap under a 1024MB
432
+ * ceiling that was in force the whole time. hub-main had exactly this shape and
433
+ * its reclaimer is proven live — 11 passes in 4h, 375–1652MB returned per pass
434
+ * at 550–680ms each. Same mechanism, same `shouldReclaim` stranded-over-trigger
435
+ * gate; never a second implementation.
433
436
  *
434
437
  * ## The stall, priced for the runners that get it
435
438
  *
@@ -447,13 +450,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
447
450
  function startRunnerHeapWatch(options) {
448
451
  if (options.heapProfile !== "heavy") return void 0;
449
452
  const intervalMs = options.intervalMs ?? 3e5;
450
- if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
453
+ if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
451
454
  let reclaimOptions = options.reclaimOptions;
452
455
  if (reclaimOptions === void 0) {
453
456
  const reclaimer = createV8Reclaimer();
454
457
  reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
455
458
  }
456
- return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
459
+ return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, true);
457
460
  }
458
461
  //#endregion
459
462
  //#region src/kernel/moleculer/addon-data-plane-facility.ts
@@ -307,7 +307,7 @@ function format(label, s, loop) {
307
307
  * `unref`ed so it can never hold the process open, and every read is wrapped:
308
308
  * a diagnostic that can crash the process it watches is worse than none.
309
309
  */
310
- function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv) {
310
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
311
311
  const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
312
312
  const now = reclaimOptions?.now ?? (() => Date.now());
313
313
  const triggerMb = reclaimOptions?.triggerMb ?? 1024;
@@ -388,7 +388,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
388
388
  const timer = setInterval(tick, probeIntervalMs);
389
389
  timer.unref?.();
390
390
  tick();
391
- if (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.`);
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.`);
392
392
  let stopped = false;
393
393
  return () => {
394
394
  if (stopped) return;
@@ -418,14 +418,17 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
418
418
  *
419
419
  * ## Why heavy runners get hub-main's mechanism
420
420
  *
421
- * The media-path runners deliberately run with NO old-space ceiling
422
- * (`maxOldSpaceMb: 0` a flat cap turned a disk-stall backlog into
423
- * `Ineffective mark-compacts near heap limit` and killed recording, twice on
424
- * 2026-08-17). Uncapped, V8 feels no pressure, so a one-off burst becomes the
425
- * runner's permanent RSS: the recorder measured 2122MB holding a ~50MB index.
426
- * hub-main had exactly this shape and its reclaimer is proven live — 11 passes
427
- * in 4h, 375–1652MB returned per pass at 550–680ms each. Same mechanism, same
428
- * `shouldReclaim` stranded-over-trigger gate; never a second implementation.
421
+ * Because a heap ceiling cannot reach what actually strands in them. Every
422
+ * media-path runner now carries an old-space ceiling sized from its own
423
+ * measured peak (`addon-pipeline` manifest + its `manifest-heap-ceilings` spec),
424
+ * and that changes nothing here: what a heavy runner strands is NATIVE —
425
+ * `external`, `arrayBuffers`, glibc arena free lists none of which
426
+ * `--max-old-space-size` bounds. Measured 2026-08-25, `hub/pipeline-analytics`
427
+ * reported `stranded=2552MB` while holding an 81MB V8 heap under a 1024MB
428
+ * ceiling that was in force the whole time. hub-main had exactly this shape and
429
+ * its reclaimer is proven live — 11 passes in 4h, 375–1652MB returned per pass
430
+ * at 550–680ms each. Same mechanism, same `shouldReclaim` stranded-over-trigger
431
+ * gate; never a second implementation.
429
432
  *
430
433
  * ## The stall, priced for the runners that get it
431
434
  *
@@ -443,13 +446,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
443
446
  function startRunnerHeapWatch(options) {
444
447
  if (options.heapProfile !== "heavy") return void 0;
445
448
  const intervalMs = options.intervalMs ?? 3e5;
446
- if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
449
+ if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
447
450
  let reclaimOptions = options.reclaimOptions;
448
451
  if (reclaimOptions === void 0) {
449
452
  const reclaimer = createV8Reclaimer();
450
453
  reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
451
454
  }
452
- return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
455
+ return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, true);
453
456
  }
454
457
  //#endregion
455
458
  //#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.123",
3
+ "version": "1.2.125",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",