@camstack/system 1.2.132 → 1.2.133

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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-BTkFwAk_.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-C9x9vrqt.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-Dmcnb287.mjs";
1
+ import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-08rqn6xN.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as path$1 from "node:path";
@@ -142,3 +142,27 @@ export interface SqliteOccupancy {
142
142
  * three header reads on a connection that is already open.
143
143
  */
144
144
  export declare function sqliteOccupancy(counters: SqlitePageCounters): SqliteOccupancy;
145
+ /**
146
+ * How often the connection re-checks whether the file has outgrown its map.
147
+ *
148
+ * The database grows by roughly 40 MB a day on this cluster, so five minutes is
149
+ * far finer than it needs to be — but the check is three arithmetic operations
150
+ * on a `stat`, and the failure it prevents is a node silently returning to
151
+ * 418 synchronous page-ins per second. Cheap insurance against a slow leak.
152
+ */
153
+ export declare const MAP_GROWTH_CHECK_INTERVAL_MS: number;
154
+ /**
155
+ * The new `mmap_size` when the file has outgrown the current one, or `null`.
156
+ *
157
+ * `sqliteMmapSizeFor` already autoscales to any user's database — but it is
158
+ * applied when the connection OPENS and never again, which is the same bug one
159
+ * level up: a file that grows during uptime keeps the map it had at boot. This
160
+ * node escaped it only because 130 MB → 858 MB spanned seventeen days of
161
+ * restarts; a node up for two months walks straight back into 70% of the file
162
+ * outside the map.
163
+ *
164
+ * GROWTH only. Shrinking after a delete buys nothing and costs a remap, and the
165
+ * file does not shrink without a `VACUUM` regardless — measured 2026-08-25,
166
+ * `freePct=1` on an 858 MB file that had been deleting rows for days.
167
+ */
168
+ export declare function resolveMapGrowth(currentMmapBytes: number, fileSizeBytes: number): number | null;
@@ -251,6 +251,20 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
251
251
  * enumerated. `unref` so it can never hold the process open, and
252
252
  * `CAMSTACK_SQLITE_STORAGE_REPORT=off` turns it off without a rebuild.
253
253
  */
254
+ /**
255
+ * Follow the file as it grows.
256
+ *
257
+ * The map is derived from the file at OPEN and never again, which is the same
258
+ * expiry one level up: a database that grows during uptime keeps the map it
259
+ * had at boot, and once the file passes it every read outside the window
260
+ * costs a synchronous page-in — 418 per second, measured 2026-08-25. This
261
+ * node escaped it only because 130 MB → 858 MB spanned seventeen days of
262
+ * restarts.
263
+ *
264
+ * `unref` so it never holds the process open, and the same kill switch as the
265
+ * storage report turns it off without a rebuild.
266
+ */
267
+ private scheduleMapGrowthCheck;
254
268
  private scheduleStorageReport;
255
269
  private logStorageReport;
256
270
  private readPragmaNumber;
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ require("./builtins/system-config/index.js");
26
26
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
27
27
  const require_file_data_plane = require("./file-data-plane-DO8KbxCe.js");
28
28
  const require_tls$1 = require("./tls-u8QCJCFE.js");
29
- const require_manifest_python_deps = require("./manifest-python-deps-BTkFwAk_.js");
29
+ const require_manifest_python_deps = require("./manifest-python-deps-C9x9vrqt.js");
30
30
  const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
31
31
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
32
32
  let zod = require("zod");
@@ -94022,6 +94022,34 @@ function positiveInt(raw, fallback) {
94022
94022
  const n = Number.parseInt(raw, 10);
94023
94023
  return Number.isFinite(n) && n > 0 ? n : fallback;
94024
94024
  }
94025
+ /**
94026
+ * The child's environment, with the allocator pair applied or REMOVED.
94027
+ *
94028
+ * `runnerNativeAllocatorEnv` returns what to add. That was sufficient while the
94029
+ * parent had nothing to inherit: omitting a key meant the child never saw it.
94030
+ * Once the image entrypoint welds the pair into the container environment — the
94031
+ * change that lets an operator drop them from the Unraid template — the child
94032
+ * INHERITS them, and a kill switch that only omits is a kill switch that does
94033
+ * nothing.
94034
+ *
94035
+ * So `CAMSTACK_RUNNER_NATIVE_ALLOCATOR=off` DELETES. And only that: the
94036
+ * non-linux path returns nothing to add for its own reasons (the pin is a pure
94037
+ * throughput cut there — measured `sharp.concurrency()` 6 on the darwin agent
94038
+ * against 1 on the linux hub) and must not be read as "remove what the operator
94039
+ * chose". Deletion is the switch's job, not absence's.
94040
+ */
94041
+ function applyRunnerNativeAllocator(parentEnv, platform = process.platform) {
94042
+ const next = { ...parentEnv };
94043
+ if (parentEnv["CAMSTACK_RUNNER_NATIVE_ALLOCATOR"] === "off") {
94044
+ delete next["MALLOC_ARENA_MAX"];
94045
+ delete next["VIPS_CONCURRENCY"];
94046
+ return next;
94047
+ }
94048
+ return {
94049
+ ...next,
94050
+ ...runnerNativeAllocatorEnv(parentEnv, platform)
94051
+ };
94052
+ }
94025
94053
  //#endregion
94026
94054
  //#region src/kernel/moleculer/process-service.ts
94027
94055
  /**
@@ -94319,7 +94347,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
94319
94347
  CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
94320
94348
  ...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
94321
94349
  ...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
94322
- ...runnerNativeAllocatorEnv(process.env),
94350
+ ...applyRunnerNativeAllocator(process.env),
94323
94351
  ...env
94324
94352
  };
94325
94353
  const heapFlags = runnerHeapFlags(addons);
package/dist/index.mjs CHANGED
@@ -25,7 +25,7 @@ import "./builtins/system-config/index.mjs";
25
25
  import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
26
26
  import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-BhKdxJgf.mjs";
27
27
  import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-CQhPGSJm.mjs";
28
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, At as buildHeapSample, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, Dt as HEAP_WATCH_INTERVAL_MS, E as ipcParentLink, Et as HEAP_RECLAIM_TRIGGER_MB, F as createUdsLogger, Ft as strandedMb, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, Mt as shouldReclaim, N as createUdsEventBus, Nt as startHeapWatch, O as HUB_CAP_FWD_ACTION, Ot as HEAP_WATCH_WARN_RATIO, P as udsChildLogToWorkerEntry, Pt as startRunnerHeapWatch, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, 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, jt as createV8Reclaimer, k as HUB_CAP_FWD_SERVICE, kt as RUNNER_HEAP_WATCH_INTERVAL_MS, 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 resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Dmcnb287.mjs";
28
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, At as buildHeapSample, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, Dt as HEAP_WATCH_INTERVAL_MS, E as ipcParentLink, Et as HEAP_RECLAIM_TRIGGER_MB, F as createUdsLogger, Ft as strandedMb, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, Mt as shouldReclaim, N as createUdsEventBus, Nt as startHeapWatch, O as HUB_CAP_FWD_ACTION, Ot as HEAP_WATCH_WARN_RATIO, P as udsChildLogToWorkerEntry, Pt as startRunnerHeapWatch, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, 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, jt as createV8Reclaimer, k as HUB_CAP_FWD_SERVICE, kt as RUNNER_HEAP_WATCH_INTERVAL_MS, 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 resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-08rqn6xN.mjs";
29
29
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
30
30
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
31
31
  import { z } from "zod";
@@ -94015,6 +94015,34 @@ function positiveInt(raw, fallback) {
94015
94015
  const n = Number.parseInt(raw, 10);
94016
94016
  return Number.isFinite(n) && n > 0 ? n : fallback;
94017
94017
  }
94018
+ /**
94019
+ * The child's environment, with the allocator pair applied or REMOVED.
94020
+ *
94021
+ * `runnerNativeAllocatorEnv` returns what to add. That was sufficient while the
94022
+ * parent had nothing to inherit: omitting a key meant the child never saw it.
94023
+ * Once the image entrypoint welds the pair into the container environment — the
94024
+ * change that lets an operator drop them from the Unraid template — the child
94025
+ * INHERITS them, and a kill switch that only omits is a kill switch that does
94026
+ * nothing.
94027
+ *
94028
+ * So `CAMSTACK_RUNNER_NATIVE_ALLOCATOR=off` DELETES. And only that: the
94029
+ * non-linux path returns nothing to add for its own reasons (the pin is a pure
94030
+ * throughput cut there — measured `sharp.concurrency()` 6 on the darwin agent
94031
+ * against 1 on the linux hub) and must not be read as "remove what the operator
94032
+ * chose". Deletion is the switch's job, not absence's.
94033
+ */
94034
+ function applyRunnerNativeAllocator(parentEnv, platform = process.platform) {
94035
+ const next = { ...parentEnv };
94036
+ if (parentEnv["CAMSTACK_RUNNER_NATIVE_ALLOCATOR"] === "off") {
94037
+ delete next["MALLOC_ARENA_MAX"];
94038
+ delete next["VIPS_CONCURRENCY"];
94039
+ return next;
94040
+ }
94041
+ return {
94042
+ ...next,
94043
+ ...runnerNativeAllocatorEnv(parentEnv, platform)
94044
+ };
94045
+ }
94018
94046
  //#endregion
94019
94047
  //#region src/kernel/moleculer/process-service.ts
94020
94048
  /**
@@ -94312,7 +94340,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
94312
94340
  CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
94313
94341
  ...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
94314
94342
  ...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
94315
- ...runnerNativeAllocatorEnv(process.env),
94343
+ ...applyRunnerNativeAllocator(process.env),
94316
94344
  ...env
94317
94345
  };
94318
94346
  const heapFlags = runnerHeapFlags(addons);
@@ -93,3 +93,20 @@ export interface RunnerNativeAllocatorEnv {
93
93
  * exists to leave.
94
94
  */
95
95
  export declare function runnerNativeAllocatorEnv(parentEnv: NodeJS.ProcessEnv, platform?: NodeJS.Platform): RunnerNativeAllocatorEnv;
96
+ /**
97
+ * The child's environment, with the allocator pair applied or REMOVED.
98
+ *
99
+ * `runnerNativeAllocatorEnv` returns what to add. That was sufficient while the
100
+ * parent had nothing to inherit: omitting a key meant the child never saw it.
101
+ * Once the image entrypoint welds the pair into the container environment — the
102
+ * change that lets an operator drop them from the Unraid template — the child
103
+ * INHERITS them, and a kill switch that only omits is a kill switch that does
104
+ * nothing.
105
+ *
106
+ * So `CAMSTACK_RUNNER_NATIVE_ALLOCATOR=off` DELETES. And only that: the
107
+ * non-linux path returns nothing to add for its own reasons (the pin is a pure
108
+ * throughput cut there — measured `sharp.concurrency()` 6 on the darwin agent
109
+ * against 1 on the linux hub) and must not be read as "remove what the operator
110
+ * chose". Deletion is the switch's job, not absence's.
111
+ */
112
+ export declare function applyRunnerNativeAllocator(parentEnv: NodeJS.ProcessEnv, platform?: NodeJS.Platform): NodeJS.ProcessEnv;
@@ -1,7 +1,7 @@
1
- import { ServiceBroker } from 'moleculer';
2
1
  import { TRPCLink } from '@trpc/client';
3
2
  import { AnyRouter } from '@trpc/server';
4
- import { LocalChildRegistry, CapCallInput } from '../transport/index.js';
3
+ import { ServiceBroker } from 'moleculer';
4
+ import { CapCallInput, LocalChildRegistry } from '../transport/index.js';
5
5
  /**
6
6
  * Resolver for in-process capability providers.
7
7
  *
@@ -51,11 +51,39 @@ export type CapUsageObserver = (obs: CapUsageObservation) => void;
51
51
  export interface CapUsageObserverOptions {
52
52
  /** The addon whose `ctx.api` produced this link chain. */
53
53
  readonly callerAddonId: string;
54
- /** Resolve which addon hosts the provider for `capName`. Return `null` to skip recording. */
54
+ /**
55
+ * Resolve which addon hosts the provider for `capName`. `null` means "not in
56
+ * the LOCAL registry" — which is every cross-process provider, and no longer
57
+ * means "do not record": see {@link UNRESOLVED_PROVIDER_ADDON_ID}.
58
+ */
55
59
  readonly providerAddonIdForCap: (capName: string) => string | null;
56
60
  /** Sink for observations. MUST NOT throw — wrapper catches anyway. */
57
61
  readonly observer: CapUsageObserver;
58
62
  }
63
+ /**
64
+ * Stands in for a provider the LOCAL registry cannot name.
65
+ *
66
+ * `providerAddonIdForCap` only searches the local registry, so it returns
67
+ * `null` for every cross-process provider — which is every forked runner,
68
+ * `sqlite-settings` included. That `null` used to drop the whole observation,
69
+ * caller and all. The result: `nodes.getCapUsageGraph`, an admin API whose
70
+ * entire job is to show caller → provider → cap traffic, returned an EMPTY
71
+ * ARRAY on the live hub while the engine logged 39 717 calls a minute to a
72
+ * single collection. An empty graph reads as "nothing is calling anything".
73
+ *
74
+ * The caller is always known — it is whoever made the call — and "who is
75
+ * hammering this cap" is the question the graph exists to answer. So the
76
+ * observation is recorded and the unknown half stays VISIBLY unknown, which is
77
+ * a fact an operator can act on, rather than absent, which is a fact nobody can
78
+ * see. Naming the provider properly needs the cluster-wide cap list; until then
79
+ * this placeholder is the honest value.
80
+ */
81
+ export declare const UNRESOLVED_PROVIDER_ADDON_ID = "(unresolved: cross-process)";
82
+ /**
83
+ * Record one cap call, if an observer is configured. Extracted so the rule —
84
+ * an unresolved provider never erases the caller — has ONE place and a test.
85
+ */
86
+ export declare function recordCapUsage(observerOpts: CapUsageObserverOptions, capName: string, methodName: string): void;
59
87
  /**
60
88
  * Non-terminating tRPC link that intercepts calls when the target
61
89
  * capability has a provider registered in the local process.
@@ -6089,8 +6089,21 @@ function createHubCapForwardService(onUnownedCall) {
6089
6089
  } } }
6090
6090
  };
6091
6091
  }
6092
- //#endregion
6093
- //#region src/kernel/moleculer/trpc-links.ts
6092
+ /**
6093
+ * Record one cap call, if an observer is configured. Extracted so the rule —
6094
+ * an unresolved provider never erases the caller — has ONE place and a test.
6095
+ */
6096
+ function recordCapUsage(observerOpts, capName, methodName) {
6097
+ try {
6098
+ observerOpts.observer({
6099
+ callerAddonId: observerOpts.callerAddonId,
6100
+ providerAddonId: observerOpts.providerAddonIdForCap(capName) ?? "(unresolved: cross-process)",
6101
+ capName,
6102
+ methodName,
6103
+ atMs: Date.now()
6104
+ });
6105
+ } catch {}
6106
+ }
6094
6107
  /** Convert camelCase → kebab-case. */
6095
6108
  function toKebab(name) {
6096
6109
  return name.replace(/[A-Z]/g, (m, i) => (i > 0 ? "-" : "") + m.toLowerCase());
@@ -6140,16 +6153,7 @@ function localProviderLink(resolver, observerOpts) {
6140
6153
  if (!provider || typeof provider !== "object") return next(opWithDefaults);
6141
6154
  const fn = Reflect.get(provider, parsed.method);
6142
6155
  if (typeof fn !== "function") return next(opWithDefaults);
6143
- if (observerOpts) try {
6144
- const providerAddonId = observerOpts.providerAddonIdForCap(parsed.capName);
6145
- if (providerAddonId) observerOpts.observer({
6146
- callerAddonId: observerOpts.callerAddonId,
6147
- providerAddonId,
6148
- capName: parsed.capName,
6149
- methodName: parsed.method,
6150
- atMs: Date.now()
6151
- });
6152
- } catch {}
6156
+ if (observerOpts) recordCapUsage(observerOpts, parsed.capName, parsed.method);
6153
6157
  return observable((observer) => {
6154
6158
  Promise.resolve(fn.call(provider, opWithDefaults.input)).then((data) => {
6155
6159
  observer.next({ result: {
@@ -6410,9 +6414,9 @@ function brokerTransportLink(broker, serviceMap, observerOpts, linkOptions) {
6410
6414
  }
6411
6415
  if (observerOpts) try {
6412
6416
  const providerAddonId = serviceName !== parsed.capName ? serviceName : observerOpts.providerAddonIdForCap(parsed.capName);
6413
- if (providerAddonId) observerOpts.observer({
6417
+ observerOpts.observer({
6414
6418
  callerAddonId: observerOpts.callerAddonId,
6415
- providerAddonId,
6419
+ providerAddonId: providerAddonId ?? "(unresolved: cross-process)",
6416
6420
  capName: parsed.capName,
6417
6421
  methodName: parsed.method,
6418
6422
  atMs: Date.now()
@@ -6093,8 +6093,21 @@ function createHubCapForwardService(onUnownedCall) {
6093
6093
  } } }
6094
6094
  };
6095
6095
  }
6096
- //#endregion
6097
- //#region src/kernel/moleculer/trpc-links.ts
6096
+ /**
6097
+ * Record one cap call, if an observer is configured. Extracted so the rule —
6098
+ * an unresolved provider never erases the caller — has ONE place and a test.
6099
+ */
6100
+ function recordCapUsage(observerOpts, capName, methodName) {
6101
+ try {
6102
+ observerOpts.observer({
6103
+ callerAddonId: observerOpts.callerAddonId,
6104
+ providerAddonId: observerOpts.providerAddonIdForCap(capName) ?? "(unresolved: cross-process)",
6105
+ capName,
6106
+ methodName,
6107
+ atMs: Date.now()
6108
+ });
6109
+ } catch {}
6110
+ }
6098
6111
  /** Convert camelCase → kebab-case. */
6099
6112
  function toKebab(name) {
6100
6113
  return name.replace(/[A-Z]/g, (m, i) => (i > 0 ? "-" : "") + m.toLowerCase());
@@ -6144,16 +6157,7 @@ function localProviderLink(resolver, observerOpts) {
6144
6157
  if (!provider || typeof provider !== "object") return next(opWithDefaults);
6145
6158
  const fn = Reflect.get(provider, parsed.method);
6146
6159
  if (typeof fn !== "function") return next(opWithDefaults);
6147
- if (observerOpts) try {
6148
- const providerAddonId = observerOpts.providerAddonIdForCap(parsed.capName);
6149
- if (providerAddonId) observerOpts.observer({
6150
- callerAddonId: observerOpts.callerAddonId,
6151
- providerAddonId,
6152
- capName: parsed.capName,
6153
- methodName: parsed.method,
6154
- atMs: Date.now()
6155
- });
6156
- } catch {}
6160
+ if (observerOpts) recordCapUsage(observerOpts, parsed.capName, parsed.method);
6157
6161
  return observable((observer) => {
6158
6162
  Promise.resolve(fn.call(provider, opWithDefaults.input)).then((data) => {
6159
6163
  observer.next({ result: {
@@ -6414,9 +6418,9 @@ function brokerTransportLink(broker, serviceMap, observerOpts, linkOptions) {
6414
6418
  }
6415
6419
  if (observerOpts) try {
6416
6420
  const providerAddonId = serviceName !== parsed.capName ? serviceName : observerOpts.providerAddonIdForCap(parsed.capName);
6417
- if (providerAddonId) observerOpts.observer({
6421
+ observerOpts.observer({
6418
6422
  callerAddonId: observerOpts.callerAddonId,
6419
- providerAddonId,
6423
+ providerAddonId: providerAddonId ?? "(unresolved: cross-process)",
6420
6424
  capName: parsed.capName,
6421
6425
  methodName: parsed.method,
6422
6426
  atMs: Date.now()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.132",
3
+ "version": "1.2.133",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",