@camstack/system 1.1.20 → 1.1.21

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-PbdHKDpj.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-Di_9ayDE.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as getWorkerNativeCapProvider, N as createUdsLoggerWithControl, P as LocalChildClient, _t as resolveAddonClass, et as getWorkerNativeCapSnapshot, gt as installManifestNativeDeps, i as createUdsAddonContext, it as validateProviderRegistrations, nt as setWorkerNativeCapsChangeListener, t as installManifestPythonDeps } from "./manifest-python-deps-X00KVYHa.mjs";
1
+ import { I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as resolveAddonClass, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-Bp7nvTNF.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as fs from "node:fs";
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
20
20
  require("./builtins/local-auth/index.js");
21
21
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
22
22
  require("./builtins/device-manager/index.js");
23
- const require_manifest_python_deps = require("./manifest-python-deps-PbdHKDpj.js");
23
+ const require_manifest_python_deps = require("./manifest-python-deps-Di_9ayDE.js");
24
24
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
25
25
  let _camstack_types_node = require("@camstack/types/node");
26
26
  let node_http = require("node:http");
@@ -93487,6 +93487,7 @@ Object.defineProperty(exports, "getFfmpegDownloadUrl", {
93487
93487
  }
93488
93488
  });
93489
93489
  exports.getModelFilePath = require_model_download_service.getModelFilePath;
93490
+ exports.getMoleculerEventStats = require_manifest_python_deps.getMoleculerEventStats;
93490
93491
  exports.getOrInitReadinessRegistry = require_manifest_python_deps.getOrInitReadinessRegistry;
93491
93492
  exports.getOrInitReadinessRegistryForClient = require_manifest_python_deps.getOrInitReadinessRegistryForClient;
93492
93493
  exports.getPidStats = require_resource_monitor.getPidStats;
@@ -93556,8 +93557,10 @@ Object.defineProperty(exports, "scopeKey", {
93556
93557
  exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
93557
93558
  exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
93558
93559
  exports.setHubConnected = require_manifest_python_deps.setHubConnected;
93560
+ exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
93559
93561
  exports.stageFrameworkLockstep = stageFrameworkLockstep;
93560
93562
  exports.stripCamstackDeps = stripCamstackDeps;
93563
+ exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
93561
93564
  exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
93562
93565
  exports.validateProviderRegistrations = require_manifest_python_deps.validateProviderRegistrations;
93563
93566
  exports.writePendingRestart = writePendingRestart;
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
18
18
  import "./builtins/local-auth/index.mjs";
19
19
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
20
20
  import "./builtins/device-manager/index.mjs";
21
- import { A as createUdsEventBus, B as CapRouteError, C as ipcParentLink, D as createHubCapForwardService, E as HUB_CAP_FWD_SERVICE, F as LocalChildRegistry, G as UdsLocalTransportServer, H as callWithServiceDiscovery, I as UDS_NO_ROUTE_PREFIX, J as FrameDecoder, K as SocketChannel, L as AGENT_CAP_FWD_ACTION, M as createUdsLogger, N as createUdsLoggerWithControl, O as createParentUnownedCallHandler, P as LocalChildClient, Q as createBrokerDeviceManagerApi, R as AGENT_CAP_FWD_SERVICE, S as ipcChildLink, T as HUB_CAP_FWD_ACTION, U as createLocalTransport, V as classifyCapRoute, W as UdsLocalTransportClient, X as buildNativeCapProxy, Y as encodeFrame, Z as buildUdsNativeCapProxy, _ as __resetCapUsageRegistryForTests, _t as resolveAddonClass, a as getWorkerDeviceRegistry, at as NATIVE_PROVIDER_SERVICE_INFIX, b as brokerTransportLink, c as setHubConnected, ct as capBareAction, d as registerEventBusService, dt as deserializeTypedArrays, f as AddonDepsManager, ft as serializeTypedArrays, g as CapUsageRegistry, gt as installManifestNativeDeps, h as createHwAccelService, ht as CapabilityUnavailableError, i as createUdsAddonContext, it as validateProviderRegistrations, j as udsChildLogToWorkerEntry, k as createUdsEventBridge, l as EVENT_TOPIC_PREFIX, lt as capServiceName, m as resolveHwAccel, mt as CapabilityHandle, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as capActionName, p as createKernelHwAccel, pt as DeviceRegistry, q as localEndpointPath, r as createAddonContext, rt as createAddonService, s as getOrInitReadinessRegistryForClient, st as capActionSuffix, t as installManifestPythonDeps, tt as mountNativeCapService, u as getBrokerEventBus, ut as parseCapAction, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as CapRouteResolver } from "./manifest-python-deps-X00KVYHa.mjs";
21
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, 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, 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 resolveHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getMoleculerEventStats, dt as capBareAction, et as buildUdsNativeCapProxy, f as registerEventBusService, ft as capServiceName, g as createKernelHwAccel, gt as DeviceRegistry, h as AddonDepsManager, 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 subscribePassthrough, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as setNodeEventInterest, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as getBrokerEventBus, ut as capActionSuffix, v as createHwAccelService, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Bp7nvTNF.mjs";
22
22
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
23
23
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
24
24
  import { request } from "node:http";
@@ -93252,4 +93252,4 @@ async function stageFrameworkLockstep(input) {
93252
93252
  return results;
93253
93253
  }
93254
93254
  //#endregion
93255
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, 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, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, 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, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, 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, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
93255
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, 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, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, 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, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, 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, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, stageFrameworkLockstep, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -33,7 +33,8 @@ export { type AddonDeploySource, isAddonDeploySource } from './moleculer/addon-d
33
33
  export { createAddonService, validateProviderRegistrations, } from './moleculer/addon-service-factory.js';
34
34
  export { createCoreCapService, CORE_CAP_SERVICE_NAME } from './moleculer/core-cap-service.js';
35
35
  export type { CoreCapAction, CoreCapServiceOptions } from './moleculer/core-cap-service.js';
36
- export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, registerEventBusService, getBrokerEventBus, EVENT_TOPIC_PREFIX, } from './moleculer/addon-context-factory.js';
36
+ export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, registerEventBusService, setNodeEventInterest, getMoleculerEventStats, subscribePassthrough, getBrokerEventBus, EVENT_TOPIC_PREFIX, } from './moleculer/addon-context-factory.js';
37
+ export type { MoleculerEventStats } from './moleculer/addon-context-factory.js';
37
38
  export { HubLogForwarder } from './moleculer/hub-log-forwarder.js';
38
39
  export type { AddonContextOptions } from './moleculer/addon-context-factory.js';
39
40
  export { createHubService } from './moleculer/hub-service.js';
@@ -5,7 +5,7 @@ import { CapabilityRegistry } from '../capability-registry.js';
5
5
  import { CapCallInput } from '../transport/index.js';
6
6
  import { LocalChildClient } from '../transport/local-child-client.js';
7
7
  import { DataPlaneSink } from './addon-data-plane-facility.js';
8
- import { getBrokerEventBus, registerEventBusService, clusterEventTopic, EVENT_TOPIC_PREFIX } from './event-bus.js';
8
+ import { getBrokerEventBus, registerEventBusService, setNodeEventInterest, getMoleculerEventStats, subscribePassthrough, clusterEventTopic, EVENT_TOPIC_PREFIX, MoleculerEventStats } from './event-bus.js';
9
9
  import { createRunnerLogger, setHubConnected, ___resetHubConnectedForTests, ___createRemoteLoggerForTests } from './remote-logger.js';
10
10
  import { withAddonScope, setPreferredProvider, ___resetBindingSubscriptionForTests } from './provider-registry.js';
11
11
  /**
@@ -99,5 +99,5 @@ declare function createUdsAddonContext(client: LocalChildClient, nodeId: string,
99
99
  * next addon instance loads.
100
100
  */
101
101
  declare function drainWorkerDisposerChain(nodeId: string, addonId: string): Promise<void>;
102
- export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, drainWorkerDisposerChain, registerEventBusService, clusterEventTopic, EVENT_TOPIC_PREFIX, getBrokerEventBus, withAddonScope, setPreferredProvider, createRunnerLogger, __resetHubConnectedForTests, __resetBindingSubscriptionForTests, __createRemoteLoggerForTests, };
103
- export type { AddonContextOptions };
102
+ export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, drainWorkerDisposerChain, registerEventBusService, setNodeEventInterest, getMoleculerEventStats, subscribePassthrough, clusterEventTopic, EVENT_TOPIC_PREFIX, getBrokerEventBus, withAddonScope, setPreferredProvider, createRunnerLogger, __resetHubConnectedForTests, __resetBindingSubscriptionForTests, __createRemoteLoggerForTests, };
103
+ export type { AddonContextOptions, MoleculerEventStats };
@@ -77,6 +77,60 @@ export declare const RING_BUFFER_DENY_PATTERNS: readonly string[];
77
77
  */
78
78
  export declare const MAX_RECENT_EVENTS = 1000;
79
79
  export declare function isHighFrequencyCategory(category: string): boolean;
80
+ /**
81
+ * Fan-out mode for cross-node (Moleculer) event delivery, read from the
82
+ * `CAMSTACK_MOLECULER_EVENT_FANOUT` env. The inter-node analogue of the
83
+ * intra-host `CAMSTACK_UDS_EVENT_FANOUT` gate (`local-child-registry.ts`):
84
+ * - `filter` (default) — drop an inbound cross-node event whose category
85
+ * no local subscriber on THIS node wants (the union of its children's
86
+ * declared patterns).
87
+ * - `shadow` — compute the drop decision + count would-suppresses, but
88
+ * STILL deliver every event (burn-in: prove zero would-be-starved deliveries
89
+ * before flipping to `filter`).
90
+ * - `broadcast` — ignore the gate entirely (pre-D2 behaviour).
91
+ *
92
+ * ROLLBACK CONTRACT (operators, read this): this env governs ONLY the inter-node
93
+ * (Moleculer) gate. The intra-host UDS fan-out has a SEPARATE, independent
94
+ * kill-switch, `CAMSTACK_UDS_EVENT_FANOUT` (see `local-child-registry.ts`). A
95
+ * FULL rollback to pre-D2 / pre-#44 delivery therefore requires BOTH env vars
96
+ * set to `broadcast` AND a process restart on EVERY node (hub + all agents) —
97
+ * each is read ONCE at registration/construction, so a running process will not
98
+ * pick up a change. Flipping only one leg leaves the other filtering and yields
99
+ * inconsistent behaviour. Recommended first fleet-wide enable: `shadow` on both,
100
+ * watch the suppressed counters (`getMoleculerEventStats` /
101
+ * `getChildEventStats`), then flip to `filter` once burn-in shows zero
102
+ * would-be-starved deliveries.
103
+ */
104
+ export type MoleculerEventFanoutMode = 'filter' | 'shadow' | 'broadcast';
105
+ export declare function readMoleculerFanoutMode(): MoleculerEventFanoutMode;
106
+ /**
107
+ * A node's inbound-interest snapshot used by {@link crossNodeAcceptsCategory}.
108
+ * - `retainRecent` — the receiving bus retains events for `getRecent`/audit
109
+ * (the hub main process). Such a bus genuinely wants EVERY category (its
110
+ * interest set is the admin-ui live stream + recent[] ring), so it always
111
+ * fails OPEN — it never filters inbound cross-node events.
112
+ * - `interest` — the union of this node's local subscribers' declared category
113
+ * patterns (aggregated from its UDS children). `null` = undeclared / unknown
114
+ * (a legacy child, or no oracle installed) → fail OPEN (accept everything).
115
+ * An EMPTY array is a *declared* "wants nothing" and drops every category.
116
+ */
117
+ export interface CrossNodeInterestSnapshot {
118
+ readonly retainRecent: boolean;
119
+ readonly interest: readonly string[] | null;
120
+ }
121
+ /**
122
+ * Does this node want to accept an inbound cross-node event in `category`?
123
+ * The receiving-side mirror of `LocalChildRegistry.childWantsEvent`:
124
+ * - `broadcast` mode → always (gate disabled).
125
+ * - a retaining bus (hub main) → always (it serves `getRecent` + admin-ui;
126
+ * its interest is genuinely everything).
127
+ * - undeclared interest (`null`) → always (fail-open).
128
+ * - else the declared union must match via `matchesPattern` — the SAME
129
+ * function the local subscriber map-key gate uses, giving delivery parity.
130
+ * `shadow` is handled by the caller (it counts the would-suppress but still
131
+ * delivers), exactly like the UDS fan-out.
132
+ */
133
+ export declare function crossNodeAcceptsCategory(mode: MoleculerEventFanoutMode, snapshot: CrossNodeInterestSnapshot, category: string): boolean;
80
134
  /**
81
135
  * Deliver `event` to all matching local subscribers and — ONLY on a bus that
82
136
  * opted into retention (`state.retainRecent`, i.e. the hub main process) and
@@ -1,4 +1,4 @@
1
- import { IEventBus } from '@camstack/types';
1
+ import { IEventBus, SystemEvent } from '@camstack/types';
2
2
  import { ServiceBroker } from 'moleculer';
3
3
  /**
4
4
  * Narrow interface for the Moleculer ServiceBroker surface used in this file.
@@ -28,6 +28,39 @@ export interface BrokerLike {
28
28
  export declare const EVENT_TOPIC_PREFIX = "camstack.evt.";
29
29
  /** Build the Moleculer broadcast topic for an event category. */
30
30
  export declare function clusterEventTopic(category: string): string;
31
+ /**
32
+ * Subscribe a PASS-THROUGH relay handler to the broker's shared bus WITHOUT
33
+ * registering it in the local-interest surface. Used exclusively by the UDS
34
+ * event bridge for its cluster/parent-local → children fan-out subscription:
35
+ * the bridge must receive every event the gate lets through so it can relay to
36
+ * forked children, but it is not itself a terminal consumer, so it must not
37
+ * count toward this node's interest (a counted `*` would defeat the filter).
38
+ * Returns an unsubscribe function.
39
+ */
40
+ export declare function subscribePassthrough(broker: ServiceBroker, handler: (event: SystemEvent) => void): () => void;
41
+ /** Cross-node inbound fan-out counters for a broker, `{ delivered, suppressed }`. */
42
+ export interface MoleculerEventStats {
43
+ readonly delivered: number;
44
+ readonly suppressed: number;
45
+ }
46
+ /**
47
+ * Install (or clear, with `null`) the per-node interest oracle consulted by the
48
+ * `$event-bus` inbound handler to decide whether an inbound cross-node event is
49
+ * wanted by any local subscriber on this node. The oracle is read PER-EVENT so a
50
+ * child (un)subscribing takes effect immediately with no re-registration.
51
+ *
52
+ * Only non-retaining nodes (agents) meaningfully filter — a retaining bus (hub
53
+ * main) always accepts every category (it serves `getRecent` + the admin-ui live
54
+ * stream). Installing the oracle on the hub too is therefore harmless and keeps
55
+ * the wiring uniform.
56
+ */
57
+ export declare function setNodeEventInterest(broker: ServiceBroker, oracle: (() => readonly string[] | null) | null): void;
58
+ /**
59
+ * Cross-node inbound counters for `broker`, or `null` if no cross-node event has
60
+ * been processed yet. Exposed for the D2 shadow burn-in + operator debug —
61
+ * mirror of `LocalChildRegistry.getChildEventStats`.
62
+ */
63
+ export declare function getMoleculerEventStats(broker: ServiceBroker): MoleculerEventStats | null;
31
64
  /**
32
65
  * Register the `$event-bus` service on a broker. MUST be called BEFORE
33
66
  * `broker.start()` — Moleculer announces a service's event
@@ -123,6 +123,29 @@ export declare class LocalChildRegistry {
123
123
  * that explicitly subscribes to a ring-denied category must still RECEIVE it.
124
124
  */
125
125
  private childWantsEvent;
126
+ /**
127
+ * D2: this node's aggregate cross-node event interest — the UNION of every
128
+ * connected child's declared category patterns. Consumed by the parent's
129
+ * `$event-bus` inbound gate (via `setNodeEventInterest`) so the node only
130
+ * accepts cross-node (Moleculer) categories at least one local child wants.
131
+ *
132
+ * Returns `null` (fail-open — accept everything) when:
133
+ * - NO child is currently connected (boot window before the first forked
134
+ * child completes its UDS handshake, or every child has disconnected): a
135
+ * node MUST NOT drop 100% of inbound cross-node events during that window,
136
+ * and a child may connect imminently. This is distinct from the
137
+ * declared-empty case below.
138
+ * - ANY connected child is UNDECLARED (`eventPatterns === null`, a legacy
139
+ * runner): a node cannot safely narrow its inbound set while a child's
140
+ * real interest is unknown. Mirrors the per-child fail-open in
141
+ * {@link childWantsEvent}, aggregated conservatively.
142
+ *
143
+ * Returns an EMPTY array only when ≥1 child is connected and EVERY connected
144
+ * child declared an explicit empty set — a genuine "wants nothing". Reads the
145
+ * live `children` map, so a child registering / disconnecting / sending
146
+ * `event-sub` is reflected on the next call with no extra bookkeeping.
147
+ */
148
+ aggregateEventInterest(): readonly string[] | null;
126
149
  private statsFor;
127
150
  /** Deduped INFO line whenever a child's declared pattern set changes. */
128
151
  private logPatternSet;
@@ -32,6 +32,18 @@ export interface UdsEventBridgeDeps {
32
32
  readonly parentBus: IEventBus;
33
33
  /** This process's Moleculer node ID (e.g. `'hub'` or `'agent-a1b2c3'`). */
34
34
  readonly parentNodeId: string;
35
+ /**
36
+ * D2: subscribe the bridge's cluster/parent-local → children RELAY handler to
37
+ * the parent bus WITHOUT registering it in the node's local-interest surface
38
+ * (`subscribePassthrough` from `@camstack/system`). The bridge is a conduit to
39
+ * forked children, not a terminal local consumer — counting its wildcard `*`
40
+ * subscription would make every cross-node category appear "wanted" and defeat
41
+ * the inbound gate. When omitted (tests / legacy callers) the bridge falls
42
+ * back to `parentBus.subscribe({}, …)`, which is functionally correct but
43
+ * counts toward local interest — acceptable only where the D2 gate is not
44
+ * under test.
45
+ */
46
+ readonly subscribePassthrough?: (handler: (event: SystemEvent) => void) => () => void;
35
47
  }
36
48
  /**
37
49
  * Create the parent-side UDS ↔ Moleculer event bridge.
@@ -3459,6 +3459,29 @@ function isHighFrequencyCategory(category) {
3459
3459
  for (const pattern of RING_BUFFER_DENY_PATTERNS) if (matchesPattern(pattern, category)) return true;
3460
3460
  return false;
3461
3461
  }
3462
+ function readMoleculerFanoutMode() {
3463
+ const raw = process.env.CAMSTACK_MOLECULER_EVENT_FANOUT;
3464
+ if (raw === "shadow" || raw === "broadcast") return raw;
3465
+ return "filter";
3466
+ }
3467
+ /**
3468
+ * Does this node want to accept an inbound cross-node event in `category`?
3469
+ * The receiving-side mirror of `LocalChildRegistry.childWantsEvent`:
3470
+ * - `broadcast` mode → always (gate disabled).
3471
+ * - a retaining bus (hub main) → always (it serves `getRecent` + admin-ui;
3472
+ * its interest is genuinely everything).
3473
+ * - undeclared interest (`null`) → always (fail-open).
3474
+ * - else the declared union must match via `matchesPattern` — the SAME
3475
+ * function the local subscriber map-key gate uses, giving delivery parity.
3476
+ * `shadow` is handled by the caller (it counts the would-suppress but still
3477
+ * delivers), exactly like the UDS fan-out.
3478
+ */
3479
+ function crossNodeAcceptsCategory(mode, snapshot, category) {
3480
+ if (mode === "broadcast") return true;
3481
+ if (snapshot.retainRecent) return true;
3482
+ if (snapshot.interest === null) return true;
3483
+ return snapshot.interest.some((p) => matchesPattern(p, category));
3484
+ }
3462
3485
  /**
3463
3486
  * Deliver `event` to all matching local subscribers and — ONLY on a bus that
3464
3487
  * opted into retention (`state.retainRecent`, i.e. the hub main process) and
@@ -3975,6 +3998,37 @@ var LocalChildRegistry = class {
3975
3998
  if (entry.eventPatterns === null) return true;
3976
3999
  return entry.eventPatterns.some((p) => matchesPattern(p, category));
3977
4000
  }
4001
+ /**
4002
+ * D2: this node's aggregate cross-node event interest — the UNION of every
4003
+ * connected child's declared category patterns. Consumed by the parent's
4004
+ * `$event-bus` inbound gate (via `setNodeEventInterest`) so the node only
4005
+ * accepts cross-node (Moleculer) categories at least one local child wants.
4006
+ *
4007
+ * Returns `null` (fail-open — accept everything) when:
4008
+ * - NO child is currently connected (boot window before the first forked
4009
+ * child completes its UDS handshake, or every child has disconnected): a
4010
+ * node MUST NOT drop 100% of inbound cross-node events during that window,
4011
+ * and a child may connect imminently. This is distinct from the
4012
+ * declared-empty case below.
4013
+ * - ANY connected child is UNDECLARED (`eventPatterns === null`, a legacy
4014
+ * runner): a node cannot safely narrow its inbound set while a child's
4015
+ * real interest is unknown. Mirrors the per-child fail-open in
4016
+ * {@link childWantsEvent}, aggregated conservatively.
4017
+ *
4018
+ * Returns an EMPTY array only when ≥1 child is connected and EVERY connected
4019
+ * child declared an explicit empty set — a genuine "wants nothing". Reads the
4020
+ * live `children` map, so a child registering / disconnecting / sending
4021
+ * `event-sub` is reflected on the next call with no extra bookkeeping.
4022
+ */
4023
+ aggregateEventInterest() {
4024
+ if (this.children.size === 0) return null;
4025
+ const union = /* @__PURE__ */ new Set();
4026
+ for (const entry of this.children.values()) {
4027
+ if (entry.eventPatterns === null) return null;
4028
+ for (const p of entry.eventPatterns) union.add(p);
4029
+ }
4030
+ return [...union];
4031
+ }
3978
4032
  statsFor(childId) {
3979
4033
  let s = this.childEventStats.get(childId);
3980
4034
  if (s === void 0) {
@@ -4747,7 +4801,7 @@ var MAX_RECENT = 512;
4747
4801
  * and clears the child-event handler. Call it on process shutdown.
4748
4802
  */
4749
4803
  function createUdsEventBridge(deps) {
4750
- const { registry, parentBus, parentNodeId } = deps;
4804
+ const { registry, parentBus, parentNodeId, subscribePassthrough } = deps;
4751
4805
  /**
4752
4806
  * In-memory set of event ids for events that originated from one of this
4753
4807
  * bridge's UDS children and have already been fanned to siblings + the
@@ -4766,10 +4820,11 @@ function createUdsEventBridge(deps) {
4766
4820
  }
4767
4821
  parentBus.emit(event);
4768
4822
  });
4769
- const unsubscribe = parentBus.subscribe({}, (event) => {
4823
+ const relayHandler = (event) => {
4770
4824
  if (recentlyFannedIds.has(event.id)) return;
4771
4825
  registry.broadcastEventToChildren(event, parentNodeId, void 0);
4772
- });
4826
+ };
4827
+ const unsubscribe = subscribePassthrough !== void 0 ? subscribePassthrough(relayHandler) : parentBus.subscribe({}, relayHandler);
4773
4828
  return () => {
4774
4829
  unsubscribe();
4775
4830
  registry.onChildEvent(null);
@@ -5861,6 +5916,163 @@ function getSharedBusState(broker, retainRecent = false) {
5861
5916
  var eventBusServiceInstalled = /* @__PURE__ */ new WeakSet();
5862
5917
  var brokerBusWarnTimestamps = /* @__PURE__ */ new WeakMap();
5863
5918
  /**
5919
+ * Per-broker node-interest oracle. Returns the FORKED-CHILD interest (the union
5920
+ * of this node's UDS children's declared category patterns), or `null` when it
5921
+ * is undeclared/unknown (a legacy child, or no forked child has connected yet)
5922
+ * → fail-open. Absent (no entry) is also fail-open: pre-D2 behaviour is
5923
+ * preserved until a node wires its interest via {@link setNodeEventInterest}.
5924
+ *
5925
+ * IMPORTANT: this oracle covers ONLY forked/UDS children. In-process
5926
+ * ('broker'-mode) subscribers on this same broker (e.g. each addon context's
5927
+ * `device.bindings-changed` cache-invalidation sub) are tracked SEPARATELY in
5928
+ * {@link brokerLocalInterest}. The gate unions BOTH so it can never starve a
5929
+ * live local subscriber of either kind.
5930
+ */
5931
+ var brokerNodeInterest = /* @__PURE__ */ new WeakMap();
5932
+ /**
5933
+ * Per-broker IN-PROCESS subscriber interest: category pattern → live refcount.
5934
+ * Populated by `getBrokerEventBus(...).subscribe` (every in-process /
5935
+ * 'broker'-mode subscription) and decremented by its unsubscribe. Does NOT
5936
+ * include the UDS event bridge's pass-through relay subscription — that is a
5937
+ * conduit to forked children (whose real interest the oracle already reports),
5938
+ * not a genuine local consumer, so it registers via {@link subscribePassthrough}
5939
+ * which bypasses this surface. Keeping the bridge out is what lets the gate
5940
+ * actually filter (otherwise the bridge's `*` would make every category wanted).
5941
+ */
5942
+ var brokerLocalInterest = /* @__PURE__ */ new WeakMap();
5943
+ function bumpLocalInterest(broker, pattern) {
5944
+ let m = brokerLocalInterest.get(broker);
5945
+ if (m === void 0) {
5946
+ m = /* @__PURE__ */ new Map();
5947
+ brokerLocalInterest.set(broker, m);
5948
+ }
5949
+ m.set(pattern, (m.get(pattern) ?? 0) + 1);
5950
+ }
5951
+ function dropLocalInterest(broker, pattern) {
5952
+ const m = brokerLocalInterest.get(broker);
5953
+ if (m === void 0) return;
5954
+ const next = (m.get(pattern) ?? 0) - 1;
5955
+ if (next <= 0) m.delete(pattern);
5956
+ else m.set(pattern, next);
5957
+ }
5958
+ /** Snapshot of the in-process subscriber category patterns for `broker`. */
5959
+ function localSubscriberPatterns(broker) {
5960
+ const m = brokerLocalInterest.get(broker);
5961
+ return m === void 0 ? [] : [...m.keys()];
5962
+ }
5963
+ /**
5964
+ * Subscribe a PASS-THROUGH relay handler to the broker's shared bus WITHOUT
5965
+ * registering it in the local-interest surface. Used exclusively by the UDS
5966
+ * event bridge for its cluster/parent-local → children fan-out subscription:
5967
+ * the bridge must receive every event the gate lets through so it can relay to
5968
+ * forked children, but it is not itself a terminal consumer, so it must not
5969
+ * count toward this node's interest (a counted `*` would defeat the filter).
5970
+ * Returns an unsubscribe function.
5971
+ */
5972
+ function subscribePassthrough(broker, handler) {
5973
+ const state = getSharedBusState(broker);
5974
+ const entry = {
5975
+ filter: {},
5976
+ categoryPattern: "*",
5977
+ handler
5978
+ };
5979
+ const set = state.handlers.get("*") ?? /* @__PURE__ */ new Set();
5980
+ set.add(entry);
5981
+ state.handlers.set("*", set);
5982
+ return () => {
5983
+ state.handlers.get("*")?.delete(entry);
5984
+ };
5985
+ }
5986
+ /** Per-broker cross-node inbound counters (delivered / would-suppress). */
5987
+ var brokerCrossNodeStats = /* @__PURE__ */ new WeakMap();
5988
+ /**
5989
+ * Install (or clear, with `null`) the per-node interest oracle consulted by the
5990
+ * `$event-bus` inbound handler to decide whether an inbound cross-node event is
5991
+ * wanted by any local subscriber on this node. The oracle is read PER-EVENT so a
5992
+ * child (un)subscribing takes effect immediately with no re-registration.
5993
+ *
5994
+ * Only non-retaining nodes (agents) meaningfully filter — a retaining bus (hub
5995
+ * main) always accepts every category (it serves `getRecent` + the admin-ui live
5996
+ * stream). Installing the oracle on the hub too is therefore harmless and keeps
5997
+ * the wiring uniform.
5998
+ */
5999
+ function setNodeEventInterest(broker, oracle) {
6000
+ if (oracle === null) brokerNodeInterest.delete(broker);
6001
+ else brokerNodeInterest.set(broker, oracle);
6002
+ }
6003
+ /**
6004
+ * Cross-node inbound counters for `broker`, or `null` if no cross-node event has
6005
+ * been processed yet. Exposed for the D2 shadow burn-in + operator debug —
6006
+ * mirror of `LocalChildRegistry.getChildEventStats`.
6007
+ */
6008
+ function getMoleculerEventStats(broker) {
6009
+ const s = brokerCrossNodeStats.get(broker);
6010
+ return s === void 0 ? null : {
6011
+ delivered: s.delivered,
6012
+ suppressed: s.suppressed
6013
+ };
6014
+ }
6015
+ function crossNodeStatsFor(broker) {
6016
+ let s = brokerCrossNodeStats.get(broker);
6017
+ if (s === void 0) {
6018
+ s = {
6019
+ delivered: 0,
6020
+ suppressed: 0
6021
+ };
6022
+ brokerCrossNodeStats.set(broker, s);
6023
+ }
6024
+ return s;
6025
+ }
6026
+ /**
6027
+ * Compute this node's COMBINED cross-node interest snapshot for the gate:
6028
+ * - `retainRecent` — from the shared bus (hub main → accept everything).
6029
+ * - `interest` — union of (a) forked-child interest (the oracle) and (b) live
6030
+ * in-process subscriber patterns. `null` (fail-open) when the oracle reports
6031
+ * `null` (undeclared / no forked child yet / oracle threw) — we cannot
6032
+ * safely narrow while a forked child's real interest is unknown, and a boot
6033
+ * window with no children yet must not drop everything.
6034
+ *
6035
+ * The oracle invocation is wrapped in try/catch and fails OPEN on throw — it
6036
+ * sits in front of the per-handler try/catch, in the subsystem that previously
6037
+ * caused an OOM, so a throwing oracle must never break inbound delivery.
6038
+ */
6039
+ function nodeInterestSnapshot(broker) {
6040
+ const state = getSharedBusState(broker);
6041
+ let childInterest;
6042
+ try {
6043
+ const oracle = brokerNodeInterest.get(broker);
6044
+ childInterest = oracle ? oracle() : null;
6045
+ } catch {
6046
+ childInterest = null;
6047
+ }
6048
+ if (childInterest === null) return {
6049
+ retainRecent: state.retainRecent,
6050
+ interest: null
6051
+ };
6052
+ const local = localSubscriberPatterns(broker);
6053
+ const interest = [...new Set([...local, ...childInterest])];
6054
+ return {
6055
+ retainRecent: state.retainRecent,
6056
+ interest
6057
+ };
6058
+ }
6059
+ /**
6060
+ * Decide + account for an inbound cross-node event on `broker`. Returns whether
6061
+ * the event should be delivered to the local bus. Honours the
6062
+ * `CAMSTACK_MOLECULER_EVENT_FANOUT` mode (`mode`): in `shadow` it still delivers
6063
+ * but records the would-suppress; in `filter` it drops unwanted categories.
6064
+ * The interest is the UNION of forked-child AND in-process subscribers — the
6065
+ * gate never starves a live local subscriber of either kind.
6066
+ */
6067
+ function acceptInboundCrossNode(broker, mode, category) {
6068
+ const wants = crossNodeAcceptsCategory(mode, nodeInterestSnapshot(broker), category);
6069
+ const stats = crossNodeStatsFor(broker);
6070
+ if (!wants) stats.suppressed++;
6071
+ const deliver = wants || mode === "shadow";
6072
+ if (deliver) stats.delivered++;
6073
+ return deliver;
6074
+ }
6075
+ /**
5864
6076
  * Register the `$event-bus` service on a broker. MUST be called BEFORE
5865
6077
  * `broker.start()` — Moleculer announces a service's event
5866
6078
  * subscriptions to remote nodes only during the discovery handshake;
@@ -5884,6 +6096,7 @@ function registerEventBusService(broker) {
5884
6096
  if (eventBusServiceInstalled.has(broker)) return;
5885
6097
  eventBusServiceInstalled.add(broker);
5886
6098
  const bkr = broker;
6099
+ const fanoutMode = readMoleculerFanoutMode();
5887
6100
  try {
5888
6101
  bkr.createService({
5889
6102
  name: "$event-bus",
@@ -5891,6 +6104,7 @@ function registerEventBusService(broker) {
5891
6104
  const event = ctx.params;
5892
6105
  if (!event) return;
5893
6106
  if (event.sourceNodeId === bkr.nodeID) return;
6107
+ if (!acceptInboundCrossNode(broker, fanoutMode, event.category)) return;
5894
6108
  deliverShared(getSharedBusState(broker), event);
5895
6109
  } }
5896
6110
  });
@@ -5951,8 +6165,9 @@ function getBrokerEventBus(broker, options) {
5951
6165
  const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
5952
6166
  set.add(entry);
5953
6167
  state.handlers.set(pattern, set);
6168
+ bumpLocalInterest(broker, pattern);
5954
6169
  return () => {
5955
- state.handlers.get(pattern)?.delete(entry);
6170
+ if (state.handlers.get(pattern)?.delete(entry) === true) dropLocalInterest(broker, pattern);
5956
6171
  };
5957
6172
  },
5958
6173
  getRecent(filter, limit) {
@@ -6852,4 +7067,4 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
6852
7067
  await deps.installPythonRequirements(reqAbs);
6853
7068
  }
6854
7069
  //#endregion
6855
- export { getWorkerNativeCapProvider as $, createUdsEventBus as A, CapRouteError as B, ipcParentLink as C, createHubCapForwardService as D, HUB_CAP_FWD_SERVICE as E, LocalChildRegistry as F, UdsLocalTransportServer as G, callWithServiceDiscovery as H, UDS_NO_ROUTE_PREFIX as I, FrameDecoder as J, SocketChannel as K, AGENT_CAP_FWD_ACTION as L, createUdsLogger as M, createUdsLoggerWithControl as N, createParentUnownedCallHandler as O, LocalChildClient as P, createBrokerDeviceManagerApi as Q, AGENT_CAP_FWD_SERVICE as R, ipcChildLink as S, HUB_CAP_FWD_ACTION as T, createLocalTransport as U, classifyCapRoute as V, UdsLocalTransportClient as W, buildNativeCapProxy as X, encodeFrame as Y, buildUdsNativeCapProxy as Z, __resetCapUsageRegistryForTests as _, resolveAddonClass as _t, getWorkerDeviceRegistry as a, NATIVE_PROVIDER_SERVICE_INFIX as at, brokerTransportLink as b, setHubConnected as c, capBareAction as ct, registerEventBusService as d, deserializeTypedArrays as dt, getWorkerNativeCapSnapshot as et, AddonDepsManager as f, serializeTypedArrays as ft, CapUsageRegistry as g, installManifestNativeDeps as gt, createHwAccelService as h, CapabilityUnavailableError as ht, createUdsAddonContext as i, validateProviderRegistrations as it, udsChildLogToWorkerEntry as j, createUdsEventBridge as k, EVENT_TOPIC_PREFIX as l, capServiceName as lt, resolveHwAccel as m, CapabilityHandle as mt, adaptBrokerToCluster as n, setWorkerNativeCapsChangeListener as nt, getOrInitReadinessRegistry as o, capActionName as ot, createKernelHwAccel as p, DeviceRegistry as pt, localEndpointPath as q, createAddonContext as r, createAddonService as rt, getOrInitReadinessRegistryForClient as s, capActionSuffix as st, installManifestPythonDeps as t, mountNativeCapService as tt, getBrokerEventBus as u, parseCapAction as ut, getCapUsageRegistry as v, localProviderLink as w, buildLinkChain as x, brokerCallForCap as y, CapRouteResolver as z };
7070
+ export { buildNativeCapProxy as $, createHubCapForwardService as A, AGENT_CAP_FWD_ACTION as B, brokerTransportLink as C, localProviderLink as D, ipcParentLink as E, createUdsLogger as F, callWithServiceDiscovery as G, CapRouteResolver as H, createUdsLoggerWithControl as I, UdsLocalTransportServer as J, createLocalTransport as K, LocalChildClient as L, createUdsEventBridge as M, createUdsEventBus as N, HUB_CAP_FWD_ACTION as O, udsChildLogToWorkerEntry as P, encodeFrame as Q, LocalChildRegistry as R, brokerCallForCap as S, ipcChildLink as T, CapRouteError as U, AGENT_CAP_FWD_SERVICE as V, classifyCapRoute as W, localEndpointPath as X, SocketChannel as Y, FrameDecoder as Z, resolveHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, resolveAddonClass as bt, setHubConnected as c, NATIVE_PROVIDER_SERVICE_INFIX as ct, getMoleculerEventStats as d, capBareAction as dt, buildUdsNativeCapProxy as et, registerEventBusService as f, capServiceName as ft, createKernelHwAccel as g, DeviceRegistry as gt, AddonDepsManager as h, serializeTypedArrays as ht, createUdsAddonContext as i, mountNativeCapService as it, createParentUnownedCallHandler as j, HUB_CAP_FWD_SERVICE as k, EVENT_TOPIC_PREFIX as l, capActionName as lt, subscribePassthrough as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, createAddonService as ot, setNodeEventInterest as p, parseCapAction as pt, UdsLocalTransportClient as q, createAddonContext as r, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, validateProviderRegistrations as st, installManifestPythonDeps as t, createBrokerDeviceManagerApi as tt, getBrokerEventBus as u, capActionSuffix as ut, createHwAccelService as v, CapabilityUnavailableError as vt, buildLinkChain as w, getCapUsageRegistry as x, CapUsageRegistry as y, installManifestNativeDeps as yt, UDS_NO_ROUTE_PREFIX as z };
@@ -3461,6 +3461,29 @@ function isHighFrequencyCategory(category) {
3461
3461
  for (const pattern of RING_BUFFER_DENY_PATTERNS) if (matchesPattern(pattern, category)) return true;
3462
3462
  return false;
3463
3463
  }
3464
+ function readMoleculerFanoutMode() {
3465
+ const raw = process.env.CAMSTACK_MOLECULER_EVENT_FANOUT;
3466
+ if (raw === "shadow" || raw === "broadcast") return raw;
3467
+ return "filter";
3468
+ }
3469
+ /**
3470
+ * Does this node want to accept an inbound cross-node event in `category`?
3471
+ * The receiving-side mirror of `LocalChildRegistry.childWantsEvent`:
3472
+ * - `broadcast` mode → always (gate disabled).
3473
+ * - a retaining bus (hub main) → always (it serves `getRecent` + admin-ui;
3474
+ * its interest is genuinely everything).
3475
+ * - undeclared interest (`null`) → always (fail-open).
3476
+ * - else the declared union must match via `matchesPattern` — the SAME
3477
+ * function the local subscriber map-key gate uses, giving delivery parity.
3478
+ * `shadow` is handled by the caller (it counts the would-suppress but still
3479
+ * delivers), exactly like the UDS fan-out.
3480
+ */
3481
+ function crossNodeAcceptsCategory(mode, snapshot, category) {
3482
+ if (mode === "broadcast") return true;
3483
+ if (snapshot.retainRecent) return true;
3484
+ if (snapshot.interest === null) return true;
3485
+ return snapshot.interest.some((p) => matchesPattern(p, category));
3486
+ }
3464
3487
  /**
3465
3488
  * Deliver `event` to all matching local subscribers and — ONLY on a bus that
3466
3489
  * opted into retention (`state.retainRecent`, i.e. the hub main process) and
@@ -3977,6 +4000,37 @@ var LocalChildRegistry = class {
3977
4000
  if (entry.eventPatterns === null) return true;
3978
4001
  return entry.eventPatterns.some((p) => matchesPattern(p, category));
3979
4002
  }
4003
+ /**
4004
+ * D2: this node's aggregate cross-node event interest — the UNION of every
4005
+ * connected child's declared category patterns. Consumed by the parent's
4006
+ * `$event-bus` inbound gate (via `setNodeEventInterest`) so the node only
4007
+ * accepts cross-node (Moleculer) categories at least one local child wants.
4008
+ *
4009
+ * Returns `null` (fail-open — accept everything) when:
4010
+ * - NO child is currently connected (boot window before the first forked
4011
+ * child completes its UDS handshake, or every child has disconnected): a
4012
+ * node MUST NOT drop 100% of inbound cross-node events during that window,
4013
+ * and a child may connect imminently. This is distinct from the
4014
+ * declared-empty case below.
4015
+ * - ANY connected child is UNDECLARED (`eventPatterns === null`, a legacy
4016
+ * runner): a node cannot safely narrow its inbound set while a child's
4017
+ * real interest is unknown. Mirrors the per-child fail-open in
4018
+ * {@link childWantsEvent}, aggregated conservatively.
4019
+ *
4020
+ * Returns an EMPTY array only when ≥1 child is connected and EVERY connected
4021
+ * child declared an explicit empty set — a genuine "wants nothing". Reads the
4022
+ * live `children` map, so a child registering / disconnecting / sending
4023
+ * `event-sub` is reflected on the next call with no extra bookkeeping.
4024
+ */
4025
+ aggregateEventInterest() {
4026
+ if (this.children.size === 0) return null;
4027
+ const union = /* @__PURE__ */ new Set();
4028
+ for (const entry of this.children.values()) {
4029
+ if (entry.eventPatterns === null) return null;
4030
+ for (const p of entry.eventPatterns) union.add(p);
4031
+ }
4032
+ return [...union];
4033
+ }
3980
4034
  statsFor(childId) {
3981
4035
  let s = this.childEventStats.get(childId);
3982
4036
  if (s === void 0) {
@@ -4749,7 +4803,7 @@ var MAX_RECENT = 512;
4749
4803
  * and clears the child-event handler. Call it on process shutdown.
4750
4804
  */
4751
4805
  function createUdsEventBridge(deps) {
4752
- const { registry, parentBus, parentNodeId } = deps;
4806
+ const { registry, parentBus, parentNodeId, subscribePassthrough } = deps;
4753
4807
  /**
4754
4808
  * In-memory set of event ids for events that originated from one of this
4755
4809
  * bridge's UDS children and have already been fanned to siblings + the
@@ -4768,10 +4822,11 @@ function createUdsEventBridge(deps) {
4768
4822
  }
4769
4823
  parentBus.emit(event);
4770
4824
  });
4771
- const unsubscribe = parentBus.subscribe({}, (event) => {
4825
+ const relayHandler = (event) => {
4772
4826
  if (recentlyFannedIds.has(event.id)) return;
4773
4827
  registry.broadcastEventToChildren(event, parentNodeId, void 0);
4774
- });
4828
+ };
4829
+ const unsubscribe = subscribePassthrough !== void 0 ? subscribePassthrough(relayHandler) : parentBus.subscribe({}, relayHandler);
4775
4830
  return () => {
4776
4831
  unsubscribe();
4777
4832
  registry.onChildEvent(null);
@@ -5863,6 +5918,163 @@ function getSharedBusState(broker, retainRecent = false) {
5863
5918
  var eventBusServiceInstalled = /* @__PURE__ */ new WeakSet();
5864
5919
  var brokerBusWarnTimestamps = /* @__PURE__ */ new WeakMap();
5865
5920
  /**
5921
+ * Per-broker node-interest oracle. Returns the FORKED-CHILD interest (the union
5922
+ * of this node's UDS children's declared category patterns), or `null` when it
5923
+ * is undeclared/unknown (a legacy child, or no forked child has connected yet)
5924
+ * → fail-open. Absent (no entry) is also fail-open: pre-D2 behaviour is
5925
+ * preserved until a node wires its interest via {@link setNodeEventInterest}.
5926
+ *
5927
+ * IMPORTANT: this oracle covers ONLY forked/UDS children. In-process
5928
+ * ('broker'-mode) subscribers on this same broker (e.g. each addon context's
5929
+ * `device.bindings-changed` cache-invalidation sub) are tracked SEPARATELY in
5930
+ * {@link brokerLocalInterest}. The gate unions BOTH so it can never starve a
5931
+ * live local subscriber of either kind.
5932
+ */
5933
+ var brokerNodeInterest = /* @__PURE__ */ new WeakMap();
5934
+ /**
5935
+ * Per-broker IN-PROCESS subscriber interest: category pattern → live refcount.
5936
+ * Populated by `getBrokerEventBus(...).subscribe` (every in-process /
5937
+ * 'broker'-mode subscription) and decremented by its unsubscribe. Does NOT
5938
+ * include the UDS event bridge's pass-through relay subscription — that is a
5939
+ * conduit to forked children (whose real interest the oracle already reports),
5940
+ * not a genuine local consumer, so it registers via {@link subscribePassthrough}
5941
+ * which bypasses this surface. Keeping the bridge out is what lets the gate
5942
+ * actually filter (otherwise the bridge's `*` would make every category wanted).
5943
+ */
5944
+ var brokerLocalInterest = /* @__PURE__ */ new WeakMap();
5945
+ function bumpLocalInterest(broker, pattern) {
5946
+ let m = brokerLocalInterest.get(broker);
5947
+ if (m === void 0) {
5948
+ m = /* @__PURE__ */ new Map();
5949
+ brokerLocalInterest.set(broker, m);
5950
+ }
5951
+ m.set(pattern, (m.get(pattern) ?? 0) + 1);
5952
+ }
5953
+ function dropLocalInterest(broker, pattern) {
5954
+ const m = brokerLocalInterest.get(broker);
5955
+ if (m === void 0) return;
5956
+ const next = (m.get(pattern) ?? 0) - 1;
5957
+ if (next <= 0) m.delete(pattern);
5958
+ else m.set(pattern, next);
5959
+ }
5960
+ /** Snapshot of the in-process subscriber category patterns for `broker`. */
5961
+ function localSubscriberPatterns(broker) {
5962
+ const m = brokerLocalInterest.get(broker);
5963
+ return m === void 0 ? [] : [...m.keys()];
5964
+ }
5965
+ /**
5966
+ * Subscribe a PASS-THROUGH relay handler to the broker's shared bus WITHOUT
5967
+ * registering it in the local-interest surface. Used exclusively by the UDS
5968
+ * event bridge for its cluster/parent-local → children fan-out subscription:
5969
+ * the bridge must receive every event the gate lets through so it can relay to
5970
+ * forked children, but it is not itself a terminal consumer, so it must not
5971
+ * count toward this node's interest (a counted `*` would defeat the filter).
5972
+ * Returns an unsubscribe function.
5973
+ */
5974
+ function subscribePassthrough(broker, handler) {
5975
+ const state = getSharedBusState(broker);
5976
+ const entry = {
5977
+ filter: {},
5978
+ categoryPattern: "*",
5979
+ handler
5980
+ };
5981
+ const set = state.handlers.get("*") ?? /* @__PURE__ */ new Set();
5982
+ set.add(entry);
5983
+ state.handlers.set("*", set);
5984
+ return () => {
5985
+ state.handlers.get("*")?.delete(entry);
5986
+ };
5987
+ }
5988
+ /** Per-broker cross-node inbound counters (delivered / would-suppress). */
5989
+ var brokerCrossNodeStats = /* @__PURE__ */ new WeakMap();
5990
+ /**
5991
+ * Install (or clear, with `null`) the per-node interest oracle consulted by the
5992
+ * `$event-bus` inbound handler to decide whether an inbound cross-node event is
5993
+ * wanted by any local subscriber on this node. The oracle is read PER-EVENT so a
5994
+ * child (un)subscribing takes effect immediately with no re-registration.
5995
+ *
5996
+ * Only non-retaining nodes (agents) meaningfully filter — a retaining bus (hub
5997
+ * main) always accepts every category (it serves `getRecent` + the admin-ui live
5998
+ * stream). Installing the oracle on the hub too is therefore harmless and keeps
5999
+ * the wiring uniform.
6000
+ */
6001
+ function setNodeEventInterest(broker, oracle) {
6002
+ if (oracle === null) brokerNodeInterest.delete(broker);
6003
+ else brokerNodeInterest.set(broker, oracle);
6004
+ }
6005
+ /**
6006
+ * Cross-node inbound counters for `broker`, or `null` if no cross-node event has
6007
+ * been processed yet. Exposed for the D2 shadow burn-in + operator debug —
6008
+ * mirror of `LocalChildRegistry.getChildEventStats`.
6009
+ */
6010
+ function getMoleculerEventStats(broker) {
6011
+ const s = brokerCrossNodeStats.get(broker);
6012
+ return s === void 0 ? null : {
6013
+ delivered: s.delivered,
6014
+ suppressed: s.suppressed
6015
+ };
6016
+ }
6017
+ function crossNodeStatsFor(broker) {
6018
+ let s = brokerCrossNodeStats.get(broker);
6019
+ if (s === void 0) {
6020
+ s = {
6021
+ delivered: 0,
6022
+ suppressed: 0
6023
+ };
6024
+ brokerCrossNodeStats.set(broker, s);
6025
+ }
6026
+ return s;
6027
+ }
6028
+ /**
6029
+ * Compute this node's COMBINED cross-node interest snapshot for the gate:
6030
+ * - `retainRecent` — from the shared bus (hub main → accept everything).
6031
+ * - `interest` — union of (a) forked-child interest (the oracle) and (b) live
6032
+ * in-process subscriber patterns. `null` (fail-open) when the oracle reports
6033
+ * `null` (undeclared / no forked child yet / oracle threw) — we cannot
6034
+ * safely narrow while a forked child's real interest is unknown, and a boot
6035
+ * window with no children yet must not drop everything.
6036
+ *
6037
+ * The oracle invocation is wrapped in try/catch and fails OPEN on throw — it
6038
+ * sits in front of the per-handler try/catch, in the subsystem that previously
6039
+ * caused an OOM, so a throwing oracle must never break inbound delivery.
6040
+ */
6041
+ function nodeInterestSnapshot(broker) {
6042
+ const state = getSharedBusState(broker);
6043
+ let childInterest;
6044
+ try {
6045
+ const oracle = brokerNodeInterest.get(broker);
6046
+ childInterest = oracle ? oracle() : null;
6047
+ } catch {
6048
+ childInterest = null;
6049
+ }
6050
+ if (childInterest === null) return {
6051
+ retainRecent: state.retainRecent,
6052
+ interest: null
6053
+ };
6054
+ const local = localSubscriberPatterns(broker);
6055
+ const interest = [...new Set([...local, ...childInterest])];
6056
+ return {
6057
+ retainRecent: state.retainRecent,
6058
+ interest
6059
+ };
6060
+ }
6061
+ /**
6062
+ * Decide + account for an inbound cross-node event on `broker`. Returns whether
6063
+ * the event should be delivered to the local bus. Honours the
6064
+ * `CAMSTACK_MOLECULER_EVENT_FANOUT` mode (`mode`): in `shadow` it still delivers
6065
+ * but records the would-suppress; in `filter` it drops unwanted categories.
6066
+ * The interest is the UNION of forked-child AND in-process subscribers — the
6067
+ * gate never starves a live local subscriber of either kind.
6068
+ */
6069
+ function acceptInboundCrossNode(broker, mode, category) {
6070
+ const wants = crossNodeAcceptsCategory(mode, nodeInterestSnapshot(broker), category);
6071
+ const stats = crossNodeStatsFor(broker);
6072
+ if (!wants) stats.suppressed++;
6073
+ const deliver = wants || mode === "shadow";
6074
+ if (deliver) stats.delivered++;
6075
+ return deliver;
6076
+ }
6077
+ /**
5866
6078
  * Register the `$event-bus` service on a broker. MUST be called BEFORE
5867
6079
  * `broker.start()` — Moleculer announces a service's event
5868
6080
  * subscriptions to remote nodes only during the discovery handshake;
@@ -5886,6 +6098,7 @@ function registerEventBusService(broker) {
5886
6098
  if (eventBusServiceInstalled.has(broker)) return;
5887
6099
  eventBusServiceInstalled.add(broker);
5888
6100
  const bkr = broker;
6101
+ const fanoutMode = readMoleculerFanoutMode();
5889
6102
  try {
5890
6103
  bkr.createService({
5891
6104
  name: "$event-bus",
@@ -5893,6 +6106,7 @@ function registerEventBusService(broker) {
5893
6106
  const event = ctx.params;
5894
6107
  if (!event) return;
5895
6108
  if (event.sourceNodeId === bkr.nodeID) return;
6109
+ if (!acceptInboundCrossNode(broker, fanoutMode, event.category)) return;
5896
6110
  deliverShared(getSharedBusState(broker), event);
5897
6111
  } }
5898
6112
  });
@@ -5953,8 +6167,9 @@ function getBrokerEventBus(broker, options) {
5953
6167
  const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
5954
6168
  set.add(entry);
5955
6169
  state.handlers.set(pattern, set);
6170
+ bumpLocalInterest(broker, pattern);
5956
6171
  return () => {
5957
- state.handlers.get(pattern)?.delete(entry);
6172
+ if (state.handlers.get(pattern)?.delete(entry) === true) dropLocalInterest(broker, pattern);
5958
6173
  };
5959
6174
  },
5960
6175
  getRecent(filter, limit) {
@@ -7154,6 +7369,12 @@ Object.defineProperty(exports, "getCapUsageRegistry", {
7154
7369
  return getCapUsageRegistry;
7155
7370
  }
7156
7371
  });
7372
+ Object.defineProperty(exports, "getMoleculerEventStats", {
7373
+ enumerable: true,
7374
+ get: function() {
7375
+ return getMoleculerEventStats;
7376
+ }
7377
+ });
7157
7378
  Object.defineProperty(exports, "getOrInitReadinessRegistry", {
7158
7379
  enumerable: true,
7159
7380
  get: function() {
@@ -7262,12 +7483,24 @@ Object.defineProperty(exports, "setHubConnected", {
7262
7483
  return setHubConnected;
7263
7484
  }
7264
7485
  });
7486
+ Object.defineProperty(exports, "setNodeEventInterest", {
7487
+ enumerable: true,
7488
+ get: function() {
7489
+ return setNodeEventInterest;
7490
+ }
7491
+ });
7265
7492
  Object.defineProperty(exports, "setWorkerNativeCapsChangeListener", {
7266
7493
  enumerable: true,
7267
7494
  get: function() {
7268
7495
  return setWorkerNativeCapsChangeListener;
7269
7496
  }
7270
7497
  });
7498
+ Object.defineProperty(exports, "subscribePassthrough", {
7499
+ enumerable: true,
7500
+ get: function() {
7501
+ return subscribePassthrough;
7502
+ }
7503
+ });
7271
7504
  Object.defineProperty(exports, "udsChildLogToWorkerEntry", {
7272
7505
  enumerable: true,
7273
7506
  get: function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.20",
3
+ "version": "1.1.21",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",