@camstack/system 1.2.28 → 1.2.30

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 (60) hide show
  1. package/dist/addon-runner.js +3 -3
  2. package/dist/addon-runner.mjs +2 -2
  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.d.ts +0 -7
  8. package/dist/builtins/alerts/alerts.addon.js +47 -25
  9. package/dist/builtins/alerts/alerts.addon.mjs +47 -25
  10. package/dist/builtins/alerts/prune-policy.d.ts +44 -0
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.js +1 -1
  24. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  25. package/dist/builtins/loki-logging/index.js +1 -1
  26. package/dist/builtins/loki-logging/index.mjs +1 -1
  27. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  29. package/dist/builtins/platform-probe/index.js +1 -1
  30. package/dist/builtins/platform-probe/index.mjs +1 -1
  31. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  33. package/dist/builtins/snapshot/index.js +1 -1
  34. package/dist/builtins/snapshot/index.mjs +1 -1
  35. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filter-compiler.d.ts +61 -0
  38. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +50 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +8 -2
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +136 -64
  41. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +136 -64
  42. package/dist/builtins/storage-orchestrator/data-store-dispatch.d.ts +12 -0
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +18 -7
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +80 -11
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +80 -11
  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-BXmFvn3h.mjs → dist-Dr_P0DOB.mjs} +492 -166
  51. package/dist/{dist-BxtIzexq.js → dist-VIwdvws5.js} +497 -165
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +150 -2
  54. package/dist/index.mjs +146 -3
  55. package/dist/kernel/addon-installer.d.ts +38 -0
  56. package/dist/kernel/heap-watch.d.ts +36 -0
  57. package/dist/kernel/transport/uds-event-bus.d.ts +4 -1
  58. package/dist/{manifest-python-deps-KZ00cbfP.js → manifest-python-deps-BqE5j0-O.js} +25 -4
  59. package/dist/{manifest-python-deps-BNOApgK0.mjs → manifest-python-deps-Ck4-9K9m.mjs} +25 -4
  60. package/package.json +7 -3
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { startHeapWatch, buildHeapSample, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, } from './kernel/heap-watch.js';
2
+ export type { HeapSample, HeapWatchSink } from './kernel/heap-watch.js';
1
3
  export { ensureBinary, downloadBinary, findInPath, getPlatformInfo, buildBinaryPath, ensureFfmpeg, getFfmpegDownloadUrl, ensurePython, installPythonPackages, installPythonRequirements, getPythonDownloadUrl, PYTHON_VERSION, } from '@camstack/types/node';
2
4
  export type { PlatformInfo } from '@camstack/types/node';
3
5
  export { ALL_CAPABILITY_DEFINITIONS, METHOD_ACCESS_MAP, expandCapMethods, isArrayOutputSchema, isCollectionArrayMethod, addonSettingsCapability, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, } from '@camstack/types';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-BxtIzexq.js");
3
+ const require_dist = require("./dist-VIwdvws5.js");
4
4
  const require_model_download_service = require("./model-download-service-hf0ookyy.js");
5
- const require_manifest_python_deps = require("./manifest-python-deps-KZ00cbfP.js");
5
+ const require_manifest_python_deps = require("./manifest-python-deps-BqE5j0-O.js");
6
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
7
7
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
8
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
@@ -24,6 +24,8 @@ require("./builtins/local-auth/index.js");
24
24
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
25
25
  require("./builtins/device-manager/index.js");
26
26
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
27
+ let node_v8 = require("node:v8");
28
+ node_v8 = require_chunk.__toESM(node_v8);
27
29
  let _camstack_types_node = require("@camstack/types/node");
28
30
  let zod = require("zod");
29
31
  let node_http = require("node:http");
@@ -52,6 +54,100 @@ fs = require_chunk.__toESM(fs, 1);
52
54
  let path = require("path");
53
55
  path = require_chunk.__toESM(path, 1);
54
56
  let module$1 = require("module");
57
+ //#region src/kernel/heap-watch.ts
58
+ /**
59
+ * heap-watch — hub-main's own memory, on the record.
60
+ *
61
+ * ## Why this exists
62
+ *
63
+ * hub-main is launched with `--max-old-space-size=1536` (docker-entrypoint) and
64
+ * has logged NOTHING about its own heap. When it died on 2026-08-01/02 the only
65
+ * trace was V8's own stderr line:
66
+ *
67
+ * FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
68
+ *
69
+ * which carries no process name, no heap number and no history — the whole
70
+ * server restarts and the evidence is gone. Four of those in ~15h, and the one
71
+ * at 06:57:57 landed nine seconds after a viewer opened a WebRTC session, which
72
+ * is why the app sat in a permanent "loading": the hub was dying as it
73
+ * connected.
74
+ *
75
+ * `[mem]` reports already exist for forked addon runners (SIGUSR2 in
76
+ * `addon-runner.ts`) — hub-main, the one process that actually OOMs, was the
77
+ * one process with no telemetry at all.
78
+ *
79
+ * ## What it gives
80
+ *
81
+ * A heartbeat carrying the numbers that separate the two possible stories, and
82
+ * a WARN as the heap approaches the ceiling — the line that will exist BEFORE
83
+ * the next fatal instead of after it:
84
+ *
85
+ * - `heapUsedMb` climbing steadily between restarts → a leak;
86
+ * - `heapUsedMb` flat with a sharp jump under load → a burst allocation, and
87
+ * the timestamp says what was being served.
88
+ *
89
+ * `external`/`arrayBuffers` ride along because they are NOT bounded by
90
+ * `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
91
+ * ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
92
+ * misreads that process by 500MB.
93
+ */
94
+ /** Heartbeat period. Slow enough to be free, fast enough that the sample before
95
+ * a fatal is recent. */
96
+ var HEAP_WATCH_INTERVAL_MS = 6e4;
97
+ /** Warn above this share of the heap ceiling. Chosen so the warning precedes
98
+ * the fatal by more than one interval under realistic growth. */
99
+ var HEAP_WATCH_WARN_RATIO = .8;
100
+ var MB = (bytes) => Math.round(bytes / 1048576);
101
+ /**
102
+ * Build a sample from raw readings. Pure so the thresholds are testable without
103
+ * allocating a gigabyte to reach them.
104
+ */
105
+ function buildHeapSample(mem, heapLimitBytes, warnRatio = HEAP_WATCH_WARN_RATIO) {
106
+ const usedRatio = heapLimitBytes > 0 ? mem.heapUsed / heapLimitBytes : 0;
107
+ return {
108
+ rssMb: MB(mem.rss),
109
+ heapUsedMb: MB(mem.heapUsed),
110
+ heapTotalMb: MB(mem.heapTotal),
111
+ heapLimitMb: MB(heapLimitBytes),
112
+ externalMb: MB(mem.external),
113
+ arrayBuffersMb: MB(mem.arrayBuffers),
114
+ usedRatio: Math.round(usedRatio * 100) / 100,
115
+ nearLimit: usedRatio >= warnRatio
116
+ };
117
+ }
118
+ var consoleSink = {
119
+ info: (line) => console.log(line),
120
+ warn: (line) => console.warn(line)
121
+ };
122
+ function format$1(label, s) {
123
+ 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`;
124
+ }
125
+ /**
126
+ * Start the heartbeat. Returns a stop function.
127
+ *
128
+ * `unref`ed so it can never hold the process open, and every read is wrapped:
129
+ * a diagnostic that can crash the process it watches is worse than none.
130
+ */
131
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
132
+ const tick = () => {
133
+ try {
134
+ const sample = buildHeapSample(process.memoryUsage(), node_v8.getHeapStatistics().heap_size_limit);
135
+ const line = format$1(label, sample);
136
+ if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
137
+ else sink.info(line);
138
+ } catch {}
139
+ };
140
+ const timer = setInterval(tick, intervalMs);
141
+ timer.unref?.();
142
+ tick();
143
+ let stopped = false;
144
+ return () => {
145
+ if (stopped) return;
146
+ stopped = true;
147
+ clearInterval(timer);
148
+ };
149
+ }
150
+ //#endregion
55
151
  //#region src/events/event-bus.ts
56
152
  var EventBus = class {
57
153
  listeners = /* @__PURE__ */ new Map();
@@ -3478,6 +3574,25 @@ function readPackageJson(pkgJsonPath) {
3478
3574
  camstackAddons: Array.isArray(addonsField) ? addonsField : null
3479
3575
  };
3480
3576
  }
3577
+ /**
3578
+ * Should the bootstrap SKIP seeding `packageName` into /data/addons?
3579
+ *
3580
+ * True only for the package the closure provides, and only when it actually
3581
+ * resolves. Both halves are load-bearing:
3582
+ *
3583
+ * - seeding a copy the closure already provides creates one that WINS over
3584
+ * the closure and is never refreshed (bootstrap is seed-only, and
3585
+ * `camstack deploy` of a framework package is rejected on an image hub), so
3586
+ * the node runs first-boot builtins forever while reporting the new
3587
+ * version;
3588
+ * - skipping it when it does NOT resolve is how a fresh deployment was once
3589
+ * made unbootable ("No addon provides required infrastructure capability
3590
+ * storage-provider"). That is why the guard is resolvability rather than a
3591
+ * blanket removal from the bootstrap list.
3592
+ */
3593
+ function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
3594
+ return packageName === "@camstack/system" && isResolvable;
3595
+ }
3481
3596
  /** Minimal no-op logger for default parameter */
3482
3597
  var noopLogger = {
3483
3598
  debug() {},
@@ -3497,6 +3612,7 @@ var AddonInstaller = class AddonInstaller {
3497
3612
  workspaceDir;
3498
3613
  installSource;
3499
3614
  logger;
3615
+ isPackageResolvable;
3500
3616
  /**
3501
3617
  * Central manifest of installed addons. Tracks version + source +
3502
3618
  * timestamps + last-backup pointer per package, written atomically
@@ -3512,6 +3628,7 @@ var AddonInstaller = class AddonInstaller {
3512
3628
  this.workspaceDir = config.workspacePackagesDir;
3513
3629
  this.logger = logger;
3514
3630
  this.manifest = new AddonManifest(config.addonsDir);
3631
+ this.isPackageResolvable = config.isPackageResolvable;
3515
3632
  if (config.installSource) this.installSource = config.installSource;
3516
3633
  else this.installSource = "npm";
3517
3634
  }
@@ -3540,6 +3657,25 @@ var AddonInstaller = class AddonInstaller {
3540
3657
  * hardcoded lists — see design spec
3541
3658
  * docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
3542
3659
  */
3660
+ /**
3661
+ * Is `packageName` reachable through normal resolution — i.e. does the
3662
+ * running closure already provide it?
3663
+ *
3664
+ * Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
3665
+ * exercise BOTH sides. That matters more than it looks: the branch that
3666
+ * still seeds is the one protecting a fresh deployment from an unbootable
3667
+ * node, and it is unreachable in a test process where `@camstack/system`
3668
+ * always resolves.
3669
+ */
3670
+ isResolvableFromClosure(packageName) {
3671
+ if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
3672
+ try {
3673
+ require.resolve(`${packageName}/package.json`);
3674
+ return true;
3675
+ } catch {
3676
+ return false;
3677
+ }
3678
+ }
3543
3679
  static deriveBootstrapList(metaPackageName) {
3544
3680
  const pkgJsonId = `${metaPackageName}/package.json`;
3545
3681
  let pkgPath;
@@ -3622,6 +3758,10 @@ var AddonInstaller = class AddonInstaller {
3622
3758
  this.logger.debug(`${packageName} — already installed, skipping`);
3623
3759
  continue;
3624
3760
  }
3761
+ if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
3762
+ this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
3763
+ continue;
3764
+ }
3625
3765
  try {
3626
3766
  if (isLocal) {
3627
3767
  const pkgDir = this.findLocalPackage(packageName);
@@ -5948,6 +6088,10 @@ var INFRA_CAPABILITIES = [
5948
6088
  name: "storage-provider",
5949
6089
  required: true
5950
6090
  },
6091
+ {
6092
+ name: "data-store-provider",
6093
+ required: true
6094
+ },
5951
6095
  {
5952
6096
  name: "storage",
5953
6097
  required: true
@@ -94119,6 +94263,8 @@ exports.FsStorageBackend = FsStorageBackend;
94119
94263
  exports.HEALTH_MONITOR_GRACE_PERIOD_MS = HEALTH_MONITOR_GRACE_PERIOD_MS;
94120
94264
  exports.HEALTH_MONITOR_RETRY_INTERVALS_MS = HEALTH_MONITOR_RETRY_INTERVALS_MS;
94121
94265
  exports.HEALTH_MONITOR_TICK_MS = HEALTH_MONITOR_TICK_MS;
94266
+ exports.HEAP_WATCH_INTERVAL_MS = HEAP_WATCH_INTERVAL_MS;
94267
+ exports.HEAP_WATCH_WARN_RATIO = HEAP_WATCH_WARN_RATIO;
94122
94268
  exports.HUB_CAP_FWD_ACTION = require_manifest_python_deps.HUB_CAP_FWD_ACTION;
94123
94269
  exports.HUB_CAP_FWD_SERVICE = require_manifest_python_deps.HUB_CAP_FWD_SERVICE;
94124
94270
  exports.HubForwarderAddon = require_builtins_hub_forwarder_index.HubForwarderAddon$1;
@@ -94193,6 +94339,7 @@ Object.defineProperty(exports, "buildBinaryPath", {
94193
94339
  }
94194
94340
  });
94195
94341
  exports.buildCapRouters = buildCapRouters;
94342
+ exports.buildHeapSample = buildHeapSample;
94196
94343
  exports.buildLinkChain = require_manifest_python_deps.buildLinkChain;
94197
94344
  exports.buildNativeCapProxy = require_manifest_python_deps.buildNativeCapProxy;
94198
94345
  exports.buildNodeManifest = buildNodeManifest;
@@ -94358,6 +94505,7 @@ exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
94358
94505
  exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
94359
94506
  exports.setHubConnected = require_manifest_python_deps.setHubConnected;
94360
94507
  exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
94508
+ exports.startHeapWatch = startHeapWatch;
94361
94509
  exports.stripCamstackDeps = stripCamstackDeps;
94362
94510
  exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
94363
94511
  exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { B as logLevelAtMost, Dt as readinessKey, Et as parseJsonUnknown$1, F as isVoidInput, I as kebabToCamel, L as lifecycleJobSchema, M as isArrayOutputSchema, N as isCollectionArrayMethod, Ot as resolveCapMount, P as isObjectInput, Tt as parseJsonObject, V as looseSchema, W as objectInputDeclaresAddonId, _t as asString$1, c as RUNTIME_DEFAULTS, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, jt as EventCategory$1, k as extractNestedAddonId, kt as scopeKey, m as addonSettingsCapability, ot as DATAPLANE_SECRET_HEADER$1, pt as ReadinessTimeoutError, q as procedureAuthKey, s as METHOD_ACCESS_MAP, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, xt as expandCapMethods, yt as emitDownForOwnedCaps } from "./dist-BXmFvn3h.mjs";
2
+ import { A as extractNestedAddonId, At as scopeKey, Dt as parseJsonUnknown$1, Et as parseJsonObject, F as isObjectInput, G as objectInputDeclaresAddonId, H as looseSchema, I as isVoidInput, J as procedureAuthKey, L as kebabToCamel, Mt as EventCategory$1, N as isArrayOutputSchema, Ot as readinessKey, P as isCollectionArrayMethod, R as lifecycleJobSchema, St as expandCapMethods, V as logLevelAtMost, _t as asNumber, at as errMsg$1, bt as emitDownForOwnedCaps, c as RUNTIME_DEFAULTS, ct as DEVICE_SETTINGS_CONTRIBUTION_METHODS, gt as asJsonObject$1, kt as resolveCapMount, lt as DEVICE_STATUS_METHOD, m as addonSettingsCapability, mt as ReadinessTimeoutError, pt as ReadinessRegistry, s as METHOD_ACCESS_MAP, st as DATAPLANE_SECRET_HEADER$1, t as ALL_CAPABILITY_DEFINITIONS, vt as asString$1, yt as createEvent } from "./dist-Dr_P0DOB.mjs";
3
3
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
4
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-BNOApgK0.mjs";
4
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Ck4-9K9m.mjs";
5
5
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
6
6
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
7
7
  import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -23,6 +23,7 @@ import "./builtins/local-auth/index.mjs";
23
23
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
24
24
  import "./builtins/device-manager/index.mjs";
25
25
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
26
+ import * as v8 from "node:v8";
26
27
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
27
28
  import { z } from "zod";
28
29
  import { request } from "node:http";
@@ -45,6 +46,100 @@ import { StringDecoder } from "node:string_decoder";
45
46
  import { lstatSync, readdir as readdir$1, readdirSync, readlinkSync, realpathSync } from "fs";
46
47
  import "path";
47
48
  import { createRequire } from "module";
49
+ //#region src/kernel/heap-watch.ts
50
+ /**
51
+ * heap-watch — hub-main's own memory, on the record.
52
+ *
53
+ * ## Why this exists
54
+ *
55
+ * hub-main is launched with `--max-old-space-size=1536` (docker-entrypoint) and
56
+ * has logged NOTHING about its own heap. When it died on 2026-08-01/02 the only
57
+ * trace was V8's own stderr line:
58
+ *
59
+ * FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
60
+ *
61
+ * which carries no process name, no heap number and no history — the whole
62
+ * server restarts and the evidence is gone. Four of those in ~15h, and the one
63
+ * at 06:57:57 landed nine seconds after a viewer opened a WebRTC session, which
64
+ * is why the app sat in a permanent "loading": the hub was dying as it
65
+ * connected.
66
+ *
67
+ * `[mem]` reports already exist for forked addon runners (SIGUSR2 in
68
+ * `addon-runner.ts`) — hub-main, the one process that actually OOMs, was the
69
+ * one process with no telemetry at all.
70
+ *
71
+ * ## What it gives
72
+ *
73
+ * A heartbeat carrying the numbers that separate the two possible stories, and
74
+ * a WARN as the heap approaches the ceiling — the line that will exist BEFORE
75
+ * the next fatal instead of after it:
76
+ *
77
+ * - `heapUsedMb` climbing steadily between restarts → a leak;
78
+ * - `heapUsedMb` flat with a sharp jump under load → a burst allocation, and
79
+ * the timestamp says what was being served.
80
+ *
81
+ * `external`/`arrayBuffers` ride along because they are NOT bounded by
82
+ * `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
83
+ * ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
84
+ * misreads that process by 500MB.
85
+ */
86
+ /** Heartbeat period. Slow enough to be free, fast enough that the sample before
87
+ * a fatal is recent. */
88
+ var HEAP_WATCH_INTERVAL_MS = 6e4;
89
+ /** Warn above this share of the heap ceiling. Chosen so the warning precedes
90
+ * the fatal by more than one interval under realistic growth. */
91
+ var HEAP_WATCH_WARN_RATIO = .8;
92
+ var MB = (bytes) => Math.round(bytes / 1048576);
93
+ /**
94
+ * Build a sample from raw readings. Pure so the thresholds are testable without
95
+ * allocating a gigabyte to reach them.
96
+ */
97
+ function buildHeapSample(mem, heapLimitBytes, warnRatio = HEAP_WATCH_WARN_RATIO) {
98
+ const usedRatio = heapLimitBytes > 0 ? mem.heapUsed / heapLimitBytes : 0;
99
+ return {
100
+ rssMb: MB(mem.rss),
101
+ heapUsedMb: MB(mem.heapUsed),
102
+ heapTotalMb: MB(mem.heapTotal),
103
+ heapLimitMb: MB(heapLimitBytes),
104
+ externalMb: MB(mem.external),
105
+ arrayBuffersMb: MB(mem.arrayBuffers),
106
+ usedRatio: Math.round(usedRatio * 100) / 100,
107
+ nearLimit: usedRatio >= warnRatio
108
+ };
109
+ }
110
+ var consoleSink = {
111
+ info: (line) => console.log(line),
112
+ warn: (line) => console.warn(line)
113
+ };
114
+ function format$1(label, s) {
115
+ 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`;
116
+ }
117
+ /**
118
+ * Start the heartbeat. Returns a stop function.
119
+ *
120
+ * `unref`ed so it can never hold the process open, and every read is wrapped:
121
+ * a diagnostic that can crash the process it watches is worse than none.
122
+ */
123
+ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
124
+ const tick = () => {
125
+ try {
126
+ const sample = buildHeapSample(process.memoryUsage(), v8.getHeapStatistics().heap_size_limit);
127
+ const line = format$1(label, sample);
128
+ if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
129
+ else sink.info(line);
130
+ } catch {}
131
+ };
132
+ const timer = setInterval(tick, intervalMs);
133
+ timer.unref?.();
134
+ tick();
135
+ let stopped = false;
136
+ return () => {
137
+ if (stopped) return;
138
+ stopped = true;
139
+ clearInterval(timer);
140
+ };
141
+ }
142
+ //#endregion
48
143
  //#region src/events/event-bus.ts
49
144
  var EventBus = class {
50
145
  listeners = /* @__PURE__ */ new Map();
@@ -3471,6 +3566,25 @@ function readPackageJson(pkgJsonPath) {
3471
3566
  camstackAddons: Array.isArray(addonsField) ? addonsField : null
3472
3567
  };
3473
3568
  }
3569
+ /**
3570
+ * Should the bootstrap SKIP seeding `packageName` into /data/addons?
3571
+ *
3572
+ * True only for the package the closure provides, and only when it actually
3573
+ * resolves. Both halves are load-bearing:
3574
+ *
3575
+ * - seeding a copy the closure already provides creates one that WINS over
3576
+ * the closure and is never refreshed (bootstrap is seed-only, and
3577
+ * `camstack deploy` of a framework package is rejected on an image hub), so
3578
+ * the node runs first-boot builtins forever while reporting the new
3579
+ * version;
3580
+ * - skipping it when it does NOT resolve is how a fresh deployment was once
3581
+ * made unbootable ("No addon provides required infrastructure capability
3582
+ * storage-provider"). That is why the guard is resolvability rather than a
3583
+ * blanket removal from the bootstrap list.
3584
+ */
3585
+ function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
3586
+ return packageName === "@camstack/system" && isResolvable;
3587
+ }
3474
3588
  /** Minimal no-op logger for default parameter */
3475
3589
  var noopLogger = {
3476
3590
  debug() {},
@@ -3490,6 +3604,7 @@ var AddonInstaller = class AddonInstaller {
3490
3604
  workspaceDir;
3491
3605
  installSource;
3492
3606
  logger;
3607
+ isPackageResolvable;
3493
3608
  /**
3494
3609
  * Central manifest of installed addons. Tracks version + source +
3495
3610
  * timestamps + last-backup pointer per package, written atomically
@@ -3505,6 +3620,7 @@ var AddonInstaller = class AddonInstaller {
3505
3620
  this.workspaceDir = config.workspacePackagesDir;
3506
3621
  this.logger = logger;
3507
3622
  this.manifest = new AddonManifest(config.addonsDir);
3623
+ this.isPackageResolvable = config.isPackageResolvable;
3508
3624
  if (config.installSource) this.installSource = config.installSource;
3509
3625
  else this.installSource = "npm";
3510
3626
  }
@@ -3533,6 +3649,25 @@ var AddonInstaller = class AddonInstaller {
3533
3649
  * hardcoded lists — see design spec
3534
3650
  * docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
3535
3651
  */
3652
+ /**
3653
+ * Is `packageName` reachable through normal resolution — i.e. does the
3654
+ * running closure already provide it?
3655
+ *
3656
+ * Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
3657
+ * exercise BOTH sides. That matters more than it looks: the branch that
3658
+ * still seeds is the one protecting a fresh deployment from an unbootable
3659
+ * node, and it is unreachable in a test process where `@camstack/system`
3660
+ * always resolves.
3661
+ */
3662
+ isResolvableFromClosure(packageName) {
3663
+ if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
3664
+ try {
3665
+ __require.resolve(`${packageName}/package.json`);
3666
+ return true;
3667
+ } catch {
3668
+ return false;
3669
+ }
3670
+ }
3536
3671
  static deriveBootstrapList(metaPackageName) {
3537
3672
  const pkgJsonId = `${metaPackageName}/package.json`;
3538
3673
  let pkgPath;
@@ -3615,6 +3750,10 @@ var AddonInstaller = class AddonInstaller {
3615
3750
  this.logger.debug(`${packageName} — already installed, skipping`);
3616
3751
  continue;
3617
3752
  }
3753
+ if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
3754
+ this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
3755
+ continue;
3756
+ }
3618
3757
  try {
3619
3758
  if (isLocal) {
3620
3759
  const pkgDir = this.findLocalPackage(packageName);
@@ -5941,6 +6080,10 @@ var INFRA_CAPABILITIES = [
5941
6080
  name: "storage-provider",
5942
6081
  required: true
5943
6082
  },
6083
+ {
6084
+ name: "data-store-provider",
6085
+ required: true
6086
+ },
5944
6087
  {
5945
6088
  name: "storage",
5946
6089
  required: true
@@ -94065,4 +94208,4 @@ var LifecycleJobEngine = class {
94065
94208
  }
94066
94209
  };
94067
94210
  //#endregion
94068
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
94211
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, startHeapWatch, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -22,6 +22,25 @@ import { AddonManifest } from './addon-manifest.js';
22
22
  * `addonsDir/<pkg>` → `packages/<pkg>`.
23
23
  */
24
24
  export type InstallSource = 'npm' | 'local' | 'symlink';
25
+ /** The one bootstrap package the server closure carries itself. */
26
+ export declare const CLOSURE_PROVIDED_PACKAGE = "@camstack/system";
27
+ /**
28
+ * Should the bootstrap SKIP seeding `packageName` into /data/addons?
29
+ *
30
+ * True only for the package the closure provides, and only when it actually
31
+ * resolves. Both halves are load-bearing:
32
+ *
33
+ * - seeding a copy the closure already provides creates one that WINS over
34
+ * the closure and is never refreshed (bootstrap is seed-only, and
35
+ * `camstack deploy` of a framework package is rejected on an image hub), so
36
+ * the node runs first-boot builtins forever while reporting the new
37
+ * version;
38
+ * - skipping it when it does NOT resolve is how a fresh deployment was once
39
+ * made unbootable ("No addon provides required infrastructure capability
40
+ * storage-provider"). That is why the guard is resolvability rather than a
41
+ * blanket removal from the bootstrap list.
42
+ */
43
+ export declare function shouldSkipClosureProvidedSeed(packageName: string, isResolvable: boolean): boolean;
25
44
  export interface AddonInstallerConfig {
26
45
  /** Directory where addons are stored (e.g., {dataDir}/addons) */
27
46
  readonly addonsDir: string;
@@ -31,6 +50,13 @@ export interface AddonInstallerConfig {
31
50
  readonly workspacePackagesDir?: string;
32
51
  /** Install source mode. Default: auto-detect */
33
52
  readonly installSource?: InstallSource;
53
+ /**
54
+ * Override for "does the running closure already provide this package?".
55
+ * Production leaves it unset (real `require.resolve`); a spec sets it to
56
+ * reach the branch that still seeds `@camstack/system`, which is otherwise
57
+ * unreachable in a process where that package always resolves.
58
+ */
59
+ readonly isPackageResolvable?: (packageName: string) => boolean;
34
60
  }
35
61
  export type { InstalledPackage };
36
62
  /** Outcome of {@link AddonInstaller.sweepBackups}. Absolute paths. */
@@ -46,6 +72,7 @@ export declare class AddonInstaller {
46
72
  readonly workspaceDir: string | undefined;
47
73
  readonly installSource: InstallSource;
48
74
  private readonly logger;
75
+ private readonly isPackageResolvable?;
49
76
  /**
50
77
  * Central manifest of installed addons. Tracks version + source +
51
78
  * timestamps + last-backup pointer per package, written atomically
@@ -71,6 +98,17 @@ export declare class AddonInstaller {
71
98
  * hardcoded lists — see design spec
72
99
  * docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
73
100
  */
101
+ /**
102
+ * Is `packageName` reachable through normal resolution — i.e. does the
103
+ * running closure already provide it?
104
+ *
105
+ * Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
106
+ * exercise BOTH sides. That matters more than it looks: the branch that
107
+ * still seeds is the one protecting a fresh deployment from an unbootable
108
+ * node, and it is unreachable in a test process where `@camstack/system`
109
+ * always resolves.
110
+ */
111
+ private isResolvableFromClosure;
74
112
  private static deriveBootstrapList;
75
113
  /**
76
114
  * Bootstrap packages installed on first boot — derived from the
@@ -0,0 +1,36 @@
1
+ /** Heartbeat period. Slow enough to be free, fast enough that the sample before
2
+ * a fatal is recent. */
3
+ export declare const HEAP_WATCH_INTERVAL_MS = 60000;
4
+ /** Warn above this share of the heap ceiling. Chosen so the warning precedes
5
+ * the fatal by more than one interval under realistic growth. */
6
+ export declare const HEAP_WATCH_WARN_RATIO = 0.8;
7
+ export interface HeapSample {
8
+ readonly rssMb: number;
9
+ readonly heapUsedMb: number;
10
+ readonly heapTotalMb: number;
11
+ readonly heapLimitMb: number;
12
+ readonly externalMb: number;
13
+ readonly arrayBuffersMb: number;
14
+ /** heapUsed / heapLimit, 0-1. */
15
+ readonly usedRatio: number;
16
+ /** True once the heap is close enough to the ceiling to be worth shouting. */
17
+ readonly nearLimit: boolean;
18
+ }
19
+ /**
20
+ * Build a sample from raw readings. Pure so the thresholds are testable without
21
+ * allocating a gigabyte to reach them.
22
+ */
23
+ export declare function buildHeapSample(mem: NodeJS.MemoryUsage, heapLimitBytes: number, warnRatio?: number): HeapSample;
24
+ /** Minimal sink so this module stays usable before any logger exists — the
25
+ * starter runs before the kernel's logging is wired. */
26
+ export interface HeapWatchSink {
27
+ readonly info: (line: string) => void;
28
+ readonly warn: (line: string) => void;
29
+ }
30
+ /**
31
+ * Start the heartbeat. Returns a stop function.
32
+ *
33
+ * `unref`ed so it can never hold the process open, and every read is wrapped:
34
+ * a diagnostic that can crash the process it watches is worse than none.
35
+ */
36
+ export declare function startHeapWatch(label?: string, sink?: HeapWatchSink, intervalMs?: number): () => void;
@@ -5,7 +5,9 @@ import { IEventBus, SystemEvent } from '@camstack/types';
5
5
  *
6
6
  * - `emitEvent` — fire-and-forget: send a system event to the parent.
7
7
  * - `onEvent` — register the single handler for parent→child events.
8
- * (Only one handler is registered; calling again replaces the previous one.)
8
+ * (Only one handler is registered; calling again replaces the previous one.
9
+ * Because the slot is single, this module registers it exactly ONCE per
10
+ * channel — the shared-state factory below — never once per addon bus.)
9
11
  * - `updateEventPatterns` — declare this owner's live category-pattern set to
10
12
  * the parent so it can subscription-filter the event fan-out. Called after
11
13
  * every subscribe/unsubscribe with the CURRENT key set (full-set replace).
@@ -19,6 +21,7 @@ export interface UdsEventChannel {
19
21
  }
20
22
  /**
21
23
  * Create a UDS-backed `IEventBus` for use inside a forked child process.
24
+ * Returns a per-addon facade over the channel's ONE shared state (module doc).
22
25
  *
23
26
  * @param client - The UDS channel toward the parent (hub or agent).
24
27
  * @param addonId - This child's stable addon id — stamped as `sourceAddonId`
@@ -5164,7 +5164,31 @@ function udsChildLogToWorkerEntry(childId, entry) {
5164
5164
  //#endregion
5165
5165
  //#region src/kernel/transport/uds-event-bus.ts
5166
5166
  /**
5167
+ * ONE `SharedBusState` per `UdsEventChannel` (see the module doc's
5168
+ * group-runner contract). Keyed weakly so a channel torn down with its
5169
+ * process does not pin its state.
5170
+ */
5171
+ var sharedStateByChannel = /* @__PURE__ */ new WeakMap();
5172
+ /**
5173
+ * Resolve (or lazily create) the channel's shared bus state, registering the
5174
+ * single-slot `client.onEvent` handler exactly once — on first creation. The
5175
+ * handler delivers into the SHARED state, so every co-located addon's
5176
+ * subscribers see parent→child events regardless of bus construction order.
5177
+ * It does NOT call `client.emitEvent` (no echo back to the parent).
5178
+ */
5179
+ function sharedStateFor(client) {
5180
+ const existing = sharedStateByChannel.get(client);
5181
+ if (existing !== void 0) return existing;
5182
+ const created = createSharedBusState();
5183
+ sharedStateByChannel.set(client, created);
5184
+ client.onEvent((event) => {
5185
+ deliverShared(created, event);
5186
+ });
5187
+ return created;
5188
+ }
5189
+ /**
5167
5190
  * Create a UDS-backed `IEventBus` for use inside a forked child process.
5191
+ * Returns a per-addon facade over the channel's ONE shared state (module doc).
5168
5192
  *
5169
5193
  * @param client - The UDS channel toward the parent (hub or agent).
5170
5194
  * @param addonId - This child's stable addon id — stamped as `sourceAddonId`
@@ -5172,10 +5196,7 @@ function udsChildLogToWorkerEntry(childId, entry) {
5172
5196
  * events to their source addon.
5173
5197
  */
5174
5198
  function createUdsEventBus(client, addonId) {
5175
- const state = createSharedBusState();
5176
- client.onEvent((event) => {
5177
- deliverShared(state, event);
5178
- });
5199
+ const state = sharedStateFor(client);
5179
5200
  const reportPatterns = () => {
5180
5201
  client.updateEventPatterns(addonId, [...state.handlers.keys()]);
5181
5202
  };