@camstack/system 1.1.19 → 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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/index.js +19 -2
- package/dist/index.mjs +17 -3
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/moleculer/addon-context-factory.d.ts +3 -3
- package/dist/kernel/moleculer/event-bus-core.d.ts +95 -4
- package/dist/kernel/moleculer/event-bus.d.ts +44 -2
- package/dist/kernel/moleculer/process-service.d.ts +1 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +25 -4
- package/dist/kernel/transport/local-child-client.d.ts +34 -2
- package/dist/kernel/transport/local-child-registry.d.ts +69 -2
- package/dist/kernel/transport/uds-event-bridge.d.ts +22 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +6 -0
- package/dist/{manifest-python-deps-C1Dd9MSr.mjs → manifest-python-deps-Bp7nvTNF.mjs} +528 -120
- package/dist/{manifest-python-deps-Dt5XNSyd.js → manifest-python-deps-Di_9ayDE.js} +545 -119
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
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);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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");
|
|
@@ -92358,6 +92358,16 @@ function readAddonHeapProfile(spec) {
|
|
|
92358
92358
|
heapProfileCache.set(cacheKey, profile);
|
|
92359
92359
|
return profile;
|
|
92360
92360
|
}
|
|
92361
|
+
/**
|
|
92362
|
+
* Drop the cached heap profile for each addon so the NEXT `readAddonHeapProfile`
|
|
92363
|
+
* re-reads the manifest from disk. Called on operator-driven spawns / restarts
|
|
92364
|
+
* (a deploy may have changed the addon's `execution.heapProfile`); the
|
|
92365
|
+
* crash-respawn fast path deliberately does NOT invalidate — a crashing runner
|
|
92366
|
+
* has the same on-disk manifest, so re-reading it is pure overhead.
|
|
92367
|
+
*/
|
|
92368
|
+
function invalidateHeapProfiles(addons) {
|
|
92369
|
+
for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
|
|
92370
|
+
}
|
|
92361
92371
|
function manifestHasAddon(parsed, addonId) {
|
|
92362
92372
|
return readManifestAddons(parsed).some((a) => a.id === addonId);
|
|
92363
92373
|
}
|
|
@@ -92478,12 +92488,13 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92478
92488
|
CAMSTACK_RUNNER_ADDONS: JSON.stringify(addons),
|
|
92479
92489
|
CAMSTACK_PARENT_NODE_ID: parentNodeId,
|
|
92480
92490
|
CAMSTACK_DATA_DIR: dataDir,
|
|
92481
|
-
CAMSTACK_LOG_LEVEL: "
|
|
92491
|
+
CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
|
|
92482
92492
|
...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
|
|
92483
92493
|
...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
|
|
92484
92494
|
...env
|
|
92485
92495
|
};
|
|
92486
92496
|
const heapFlags = runnerHeapFlags(addons);
|
|
92497
|
+
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${isHeavyRunner(addons) ? "heavy" : "light"} flags=[${heapFlags.join(" ")}]`);
|
|
92487
92498
|
const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
|
|
92488
92499
|
env: childEnv,
|
|
92489
92500
|
stdio: [
|
|
@@ -92558,6 +92569,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92558
92569
|
const { runnerId, addons, env } = ctx.params;
|
|
92559
92570
|
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
92560
92571
|
if (!Array.isArray(addons) || addons.length === 0) throw new Error(`spawnRunner requires non-empty addons array`);
|
|
92572
|
+
invalidateHeapProfiles(addons);
|
|
92561
92573
|
const entry = spawnRunner(runnerId, addons, env);
|
|
92562
92574
|
return {
|
|
92563
92575
|
pid: entry.pid,
|
|
@@ -92632,6 +92644,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92632
92644
|
processes.delete(name);
|
|
92633
92645
|
crashSupervisor.reset(name);
|
|
92634
92646
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
92647
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92635
92648
|
const restarted = spawnRunner(name, runnerAddons);
|
|
92636
92649
|
restarted.restartCount = prevRestartCount + 1;
|
|
92637
92650
|
return {
|
|
@@ -92689,6 +92702,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92689
92702
|
}
|
|
92690
92703
|
processes.delete(name);
|
|
92691
92704
|
crashSupervisor.reset(name);
|
|
92705
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92692
92706
|
const respawned = spawnRunner(name, runnerAddons);
|
|
92693
92707
|
respawned.restartCount = prevRestartCount + 1;
|
|
92694
92708
|
});
|
|
@@ -93473,6 +93487,7 @@ Object.defineProperty(exports, "getFfmpegDownloadUrl", {
|
|
|
93473
93487
|
}
|
|
93474
93488
|
});
|
|
93475
93489
|
exports.getModelFilePath = require_model_download_service.getModelFilePath;
|
|
93490
|
+
exports.getMoleculerEventStats = require_manifest_python_deps.getMoleculerEventStats;
|
|
93476
93491
|
exports.getOrInitReadinessRegistry = require_manifest_python_deps.getOrInitReadinessRegistry;
|
|
93477
93492
|
exports.getOrInitReadinessRegistryForClient = require_manifest_python_deps.getOrInitReadinessRegistryForClient;
|
|
93478
93493
|
exports.getPidStats = require_resource_monitor.getPidStats;
|
|
@@ -93542,8 +93557,10 @@ Object.defineProperty(exports, "scopeKey", {
|
|
|
93542
93557
|
exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
|
|
93543
93558
|
exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
|
|
93544
93559
|
exports.setHubConnected = require_manifest_python_deps.setHubConnected;
|
|
93560
|
+
exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
|
|
93545
93561
|
exports.stageFrameworkLockstep = stageFrameworkLockstep;
|
|
93546
93562
|
exports.stripCamstackDeps = stripCamstackDeps;
|
|
93563
|
+
exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
|
|
93547
93564
|
exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
|
|
93548
93565
|
exports.validateProviderRegistrations = require_manifest_python_deps.validateProviderRegistrations;
|
|
93549
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
|
|
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";
|
|
@@ -92350,6 +92350,16 @@ function readAddonHeapProfile(spec) {
|
|
|
92350
92350
|
heapProfileCache.set(cacheKey, profile);
|
|
92351
92351
|
return profile;
|
|
92352
92352
|
}
|
|
92353
|
+
/**
|
|
92354
|
+
* Drop the cached heap profile for each addon so the NEXT `readAddonHeapProfile`
|
|
92355
|
+
* re-reads the manifest from disk. Called on operator-driven spawns / restarts
|
|
92356
|
+
* (a deploy may have changed the addon's `execution.heapProfile`); the
|
|
92357
|
+
* crash-respawn fast path deliberately does NOT invalidate — a crashing runner
|
|
92358
|
+
* has the same on-disk manifest, so re-reading it is pure overhead.
|
|
92359
|
+
*/
|
|
92360
|
+
function invalidateHeapProfiles(addons) {
|
|
92361
|
+
for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
|
|
92362
|
+
}
|
|
92353
92363
|
function manifestHasAddon(parsed, addonId) {
|
|
92354
92364
|
return readManifestAddons(parsed).some((a) => a.id === addonId);
|
|
92355
92365
|
}
|
|
@@ -92470,12 +92480,13 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92470
92480
|
CAMSTACK_RUNNER_ADDONS: JSON.stringify(addons),
|
|
92471
92481
|
CAMSTACK_PARENT_NODE_ID: parentNodeId,
|
|
92472
92482
|
CAMSTACK_DATA_DIR: dataDir,
|
|
92473
|
-
CAMSTACK_LOG_LEVEL: "
|
|
92483
|
+
CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
|
|
92474
92484
|
...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
|
|
92475
92485
|
...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
|
|
92476
92486
|
...env
|
|
92477
92487
|
};
|
|
92478
92488
|
const heapFlags = runnerHeapFlags(addons);
|
|
92489
|
+
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${isHeavyRunner(addons) ? "heavy" : "light"} flags=[${heapFlags.join(" ")}]`);
|
|
92479
92490
|
const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
|
|
92480
92491
|
env: childEnv,
|
|
92481
92492
|
stdio: [
|
|
@@ -92550,6 +92561,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92550
92561
|
const { runnerId, addons, env } = ctx.params;
|
|
92551
92562
|
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
92552
92563
|
if (!Array.isArray(addons) || addons.length === 0) throw new Error(`spawnRunner requires non-empty addons array`);
|
|
92564
|
+
invalidateHeapProfiles(addons);
|
|
92553
92565
|
const entry = spawnRunner(runnerId, addons, env);
|
|
92554
92566
|
return {
|
|
92555
92567
|
pid: entry.pid,
|
|
@@ -92624,6 +92636,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92624
92636
|
processes.delete(name);
|
|
92625
92637
|
crashSupervisor.reset(name);
|
|
92626
92638
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
92639
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92627
92640
|
const restarted = spawnRunner(name, runnerAddons);
|
|
92628
92641
|
restarted.restartCount = prevRestartCount + 1;
|
|
92629
92642
|
return {
|
|
@@ -92681,6 +92694,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92681
92694
|
}
|
|
92682
92695
|
processes.delete(name);
|
|
92683
92696
|
crashSupervisor.reset(name);
|
|
92697
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92684
92698
|
const respawned = spawnRunner(name, runnerAddons);
|
|
92685
92699
|
respawned.restartCount = prevRestartCount + 1;
|
|
92686
92700
|
});
|
|
@@ -93238,4 +93252,4 @@ async function stageFrameworkLockstep(input) {
|
|
|
93238
93252
|
return results;
|
|
93239
93253
|
}
|
|
93240
93254
|
//#endregion
|
|
93241
|
-
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 };
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventFilter, SystemEvent } from '@camstack/types';
|
|
2
2
|
export interface SubscriberEntry {
|
|
3
3
|
/** Original filter — kept whole so non-category dimensions are honoured per-event. */
|
|
4
4
|
readonly filter: EventFilter | string | undefined;
|
|
@@ -18,11 +18,34 @@ export interface SharedBusState {
|
|
|
18
18
|
* high-frequency categories so the buffer only accumulates audit-grade
|
|
19
19
|
* lifecycle events.
|
|
20
20
|
*
|
|
21
|
+
* ONLY populated when `retainRecent` is true (the hub main process). On every
|
|
22
|
+
* other bus this array stays empty for the process's whole lifetime.
|
|
23
|
+
*
|
|
21
24
|
* Cleared on server restart (intentional — events are volatile by design).
|
|
22
25
|
*/
|
|
23
26
|
readonly recent: SystemEvent[];
|
|
27
|
+
/**
|
|
28
|
+
* Whether this bus retains events in `recent[]` to back the
|
|
29
|
+
* `getRecent`/audit capability.
|
|
30
|
+
*
|
|
31
|
+
* Events are one-shot (fire-and-forget): ONLY the hub main process — the
|
|
32
|
+
* single process that serves `getRecent` — needs the registry, so ONLY its
|
|
33
|
+
* broker bus sets this true. Child addon runners and remote agents leave it
|
|
34
|
+
* false: they fan every event out to their local subscribers but retain
|
|
35
|
+
* nothing, so their heap can never accumulate an unbounded recent[] backlog.
|
|
36
|
+
*
|
|
37
|
+
* Mutable so the authoritative opt-in (the hub-main `getBrokerEventBus`
|
|
38
|
+
* caller) can upgrade a bus that a non-retaining caller happened to create
|
|
39
|
+
* first — see `getSharedBusState`. Never downgraded from true → false.
|
|
40
|
+
*/
|
|
41
|
+
retainRecent: boolean;
|
|
24
42
|
}
|
|
25
|
-
|
|
43
|
+
/**
|
|
44
|
+
* @param retainRecent - `true` ONLY for the hub main broker bus (it serves
|
|
45
|
+
* `getRecent`). Defaults to `false` — the safe one-shot default for child
|
|
46
|
+
* runners + agents, which retain nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createSharedBusState(retainRecent?: boolean): SharedBusState;
|
|
26
49
|
export declare function matchesPattern(pattern: string, category: string): boolean;
|
|
27
50
|
export declare function extractCategoryPattern(filter: EventFilter | string): string;
|
|
28
51
|
/**
|
|
@@ -45,9 +68,77 @@ export declare function matchesEventFilter(event: SystemEvent, filter: EventFilt
|
|
|
45
68
|
* being displaced by per-frame noise.
|
|
46
69
|
*/
|
|
47
70
|
export declare const RING_BUFFER_DENY_PATTERNS: readonly string[];
|
|
71
|
+
/**
|
|
72
|
+
* Hard cap on the `recent[]` buffer size. Even audit-grade categories that
|
|
73
|
+
* are NOT on the deny-list (e.g. `device.state-changed`) must not accumulate
|
|
74
|
+
* without bound — this is the last-N window, not a durable log. Without this
|
|
75
|
+
* cap the buffer grew forever (~26 MB/h/process across every broker, agent,
|
|
76
|
+
* and UDS-child bus), filling the hub to 16 GB.
|
|
77
|
+
*/
|
|
78
|
+
export declare const MAX_RECENT_EVENTS = 1000;
|
|
48
79
|
export declare function isHighFrequencyCategory(category: string): boolean;
|
|
49
80
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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;
|
|
134
|
+
/**
|
|
135
|
+
* Deliver `event` to all matching local subscribers and — ONLY on a bus that
|
|
136
|
+
* opted into retention (`state.retainRecent`, i.e. the hub main process) and
|
|
137
|
+
* unless the event is high-frequency — append it to the bounded `recent[]`
|
|
138
|
+
* ring.
|
|
139
|
+
*
|
|
140
|
+
* On a non-retaining bus (child runners, agents) the retention block is skipped
|
|
141
|
+
* entirely: events are one-shot / fire-and-forget, still fanned out to local
|
|
142
|
+
* subscribers but never accumulated. `getRecent` on such a bus returns `[]`.
|
|
52
143
|
*/
|
|
53
144
|
export declare function deliverShared(state: SharedBusState, event: SystemEvent): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IEventBus, SystemEvent } from '@camstack/types';
|
|
1
2
|
import { ServiceBroker } from 'moleculer';
|
|
2
|
-
import { IEventBus } from '@camstack/types';
|
|
3
3
|
/**
|
|
4
4
|
* Narrow interface for the Moleculer ServiceBroker surface used in this file.
|
|
5
5
|
* moleculer's index.d.ts chains through eventemitter2 whose package.json has
|
|
@@ -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
|
|
@@ -49,5 +82,14 @@ export declare function clusterEventTopic(category: string): string;
|
|
|
49
82
|
* - forkable process / group runner, before `broker.start()`
|
|
50
83
|
*/
|
|
51
84
|
export declare function registerEventBusService(broker: ServiceBroker): void;
|
|
52
|
-
|
|
85
|
+
/**
|
|
86
|
+
* @param options.retainRecent - Pass `true` ONLY from the hub main process
|
|
87
|
+
* (`EventBusService.attachBroker`), the single process that serves the
|
|
88
|
+
* `getRecent`/audit capability. Every other caller (agents, child runners,
|
|
89
|
+
* the per-addon wrapper, the `$event-bus` handler) omits it → the bus retains
|
|
90
|
+
* nothing and `getRecent` returns `[]`. Events are one-shot everywhere else.
|
|
91
|
+
*/
|
|
92
|
+
export declare function getBrokerEventBus(broker: ServiceBroker, options?: {
|
|
93
|
+
retainRecent?: boolean;
|
|
94
|
+
}): IEventBus;
|
|
53
95
|
export declare function createBrokerEventBus(broker: ServiceBroker, addonId: string): IEventBus;
|
|
@@ -68,5 +68,5 @@ interface ProcessServiceDeps {
|
|
|
68
68
|
readonly spawnFn?: SpawnFn;
|
|
69
69
|
}
|
|
70
70
|
declare function createProcessService(parentNodeId: string, dataDir: string, deps?: ProcessServiceDeps, parentTcpPort?: number, parentUdsPath?: string): ServiceSchema;
|
|
71
|
-
export { createProcessService };
|
|
72
71
|
export type { ProcessInfo, SpawnedProcess };
|
|
72
|
+
export { createProcessService };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IReadinessRegistryRecord, LogLevel, LogTags, SystemEvent } from '@camstack/types';
|
|
2
2
|
/**
|
|
3
3
|
* Routing descriptor a child sends so the parent can route cap calls to it.
|
|
4
4
|
* The provider implementation never crosses the wire — only these keys do.
|
|
@@ -15,6 +15,27 @@ export interface RegisterMessage {
|
|
|
15
15
|
readonly kind: 'register';
|
|
16
16
|
readonly childId: string;
|
|
17
17
|
readonly caps: readonly ChildCapDescriptor[];
|
|
18
|
+
/**
|
|
19
|
+
* Snapshot of the child's live category-pattern subscriptions (the keys of
|
|
20
|
+
* its local `SharedBusState.handlers` map). Carried IN the register frame so
|
|
21
|
+
* the parent learns the child's subscriptions race-free at boot — no separate
|
|
22
|
+
* post-connect sync can be missed. OPTIONAL: a legacy child bundling an older
|
|
23
|
+
* `@camstack/system` omits it; the parent treats an absent field as
|
|
24
|
+
* "undeclared" and fails OPEN (broadcast every event to that child). See
|
|
25
|
+
* `EventSubUpdateMessage` for post-boot updates.
|
|
26
|
+
*/
|
|
27
|
+
readonly eventPatterns?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Child → parent, fire-and-forget: the child's category-pattern subscription
|
|
31
|
+
* set changed after registration. FULL-SET REPLACE semantics — `patterns` is
|
|
32
|
+
* the complete current set, not a delta. Idempotent and order-safe on the
|
|
33
|
+
* in-order UDS socket (last write wins is correct). Sent via `channel.emit`
|
|
34
|
+
* like {@link ChildEventMessage}; carries no response.
|
|
35
|
+
*/
|
|
36
|
+
export interface EventSubUpdateMessage {
|
|
37
|
+
readonly kind: 'event-sub';
|
|
38
|
+
readonly patterns: readonly string[];
|
|
18
39
|
}
|
|
19
40
|
/** Child → parent outbound cap invocation — the child asks the parent to route a cap call it does not own. */
|
|
20
41
|
export interface CapCallOutMessage {
|
|
@@ -56,10 +77,10 @@ export interface ReadinessSnapshotRequest {
|
|
|
56
77
|
* Child → parent message bodies.
|
|
57
78
|
* `RegisterMessage`, `CapCallOutMessage`, and `ReadinessSnapshotRequest` travel
|
|
58
79
|
* as correlated request frames (`channel.request`) and carry a response.
|
|
59
|
-
* `ChildEventMessage` and `
|
|
60
|
-
* `channel.emit()` and do NOT carry a response.
|
|
80
|
+
* `ChildEventMessage`, `ChildLogMessage`, and `EventSubUpdateMessage` are sent
|
|
81
|
+
* fire-and-forget via `channel.emit()` and do NOT carry a response.
|
|
61
82
|
*/
|
|
62
|
-
export type ChildToParentRequest = RegisterMessage | CapCallOutMessage | ChildEventMessage | ChildLogMessage | ReadinessSnapshotRequest;
|
|
83
|
+
export type ChildToParentRequest = RegisterMessage | CapCallOutMessage | ChildEventMessage | ChildLogMessage | EventSubUpdateMessage | ReadinessSnapshotRequest;
|
|
63
84
|
/** Parent → child cap invocation. */
|
|
64
85
|
export interface CapCallMessage {
|
|
65
86
|
readonly kind: 'cap-call';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { IReadinessRegistryRecord, SystemEvent } from '@camstack/types';
|
|
2
|
+
import { AddonCallInput, CapCallInput, ChildCapDescriptor, ChildLogMessage } from './child-cap-protocol.js';
|
|
3
3
|
export interface LocalChildClientOptions {
|
|
4
4
|
/** Parent node id — selects the UDS endpoint this child connects to. */
|
|
5
5
|
readonly nodeId: string;
|
|
@@ -38,6 +38,24 @@ export declare class LocalChildClient {
|
|
|
38
38
|
* the latest set) instead of being lost or throwing.
|
|
39
39
|
*/
|
|
40
40
|
private latestCaps;
|
|
41
|
+
/**
|
|
42
|
+
* Per-owner (addonId) category-pattern subscription sets, and their union.
|
|
43
|
+
* The union is declared to the parent (in `RegisterMessage.eventPatterns`
|
|
44
|
+
* pre/at-connect, or via an `event-sub` emit post-connect) so the parent can
|
|
45
|
+
* subscription-filter its event fan-out. Per-owner keying keeps the union
|
|
46
|
+
* correct if multiple event buses ever share one client (group-runner case).
|
|
47
|
+
*/
|
|
48
|
+
private readonly patternsByOwner;
|
|
49
|
+
private latestEventPatterns;
|
|
50
|
+
/**
|
|
51
|
+
* Whether `updateEventPatterns` has ever been called. A client that never
|
|
52
|
+
* declared a subscription set (no event bus wired — e.g. a pure cap-call
|
|
53
|
+
* runner) omits `eventPatterns` from its register frame entirely, so the
|
|
54
|
+
* parent treats it as UNDECLARED and fails OPEN (broadcast-all). Once any
|
|
55
|
+
* owner declares (in production every addon context's framework
|
|
56
|
+
* subscriptions do), the register carries the real union — even if empty.
|
|
57
|
+
*/
|
|
58
|
+
private hasDeclaredEventPatterns;
|
|
41
59
|
/** Events and logs queued while the channel is not yet open. */
|
|
42
60
|
private readonly pendingEmits;
|
|
43
61
|
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
@@ -60,6 +78,20 @@ export declare class LocalChildClient {
|
|
|
60
78
|
/** Callbacks registered via `onConnected`. Fired on every (re)connect. */
|
|
61
79
|
private readonly connectedHandlers;
|
|
62
80
|
constructor(options: LocalChildClientOptions);
|
|
81
|
+
/**
|
|
82
|
+
* Declare an owner's live category-pattern subscription set. Stores it
|
|
83
|
+
* per-owner (keyed by `ownerId` = addonId), recomputes the union, and — only
|
|
84
|
+
* if the union changed — declares it to the parent:
|
|
85
|
+
* - pre-connect: buffered only; `start()`'s register frame carries the set.
|
|
86
|
+
* - post-connect: emitted as a fire-and-forget `event-sub` (full-set
|
|
87
|
+
* replace).
|
|
88
|
+
* Idempotent on an unchanged union (no redundant `event-sub` frames).
|
|
89
|
+
*/
|
|
90
|
+
updateEventPatterns(ownerId: string, patterns: readonly string[]): void;
|
|
91
|
+
/** Sorted, de-duplicated union across all owners' pattern sets. */
|
|
92
|
+
private computePatternUnion;
|
|
93
|
+
/** Order-insensitive equality — both inputs are already sorted unions here. */
|
|
94
|
+
private patternsEqual;
|
|
63
95
|
/**
|
|
64
96
|
* Register a callback that fires each time the client successfully connects
|
|
65
97
|
* (or reconnects) to its parent. Multiple handlers may be registered; all
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
import { IReadinessRegistryRecord, SystemEvent } from '@camstack/types';
|
|
2
|
+
import { AddonCallInput, CapCallInput, ChildLogMessage, RegisteredChild } from './child-cap-protocol.js';
|
|
1
3
|
import { LocalTransportServer } from './local-transport.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Fan-out mode for parent→child event delivery, from the
|
|
6
|
+
* `CAMSTACK_UDS_EVENT_FANOUT` env (read once at registry construction):
|
|
7
|
+
* - `filter` (default) — apply the subscription gate; a child receives a
|
|
8
|
+
* category only if one of its declared patterns matches.
|
|
9
|
+
* - `shadow` — compute the gate decision + count would-suppresses, but
|
|
10
|
+
* STILL send every event (burn-in: prove zero would-be-starved deliveries).
|
|
11
|
+
* - `broadcast` — ignore the gate entirely (today's behaviour); the operator's
|
|
12
|
+
* one-env rollback without redeploying runners.
|
|
13
|
+
*/
|
|
14
|
+
export type UdsEventFanoutMode = 'filter' | 'shadow' | 'broadcast';
|
|
15
|
+
/** Per-child event fan-out counters (sent = delivered, suppressed = gated/would-gate). */
|
|
16
|
+
export interface ChildEventStats {
|
|
17
|
+
sent: number;
|
|
18
|
+
suppressed: number;
|
|
19
|
+
}
|
|
4
20
|
/**
|
|
5
21
|
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
6
22
|
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
@@ -69,6 +85,12 @@ export declare class LocalChildRegistry {
|
|
|
69
85
|
private readonly resolveChildIdForAddon?;
|
|
70
86
|
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
71
87
|
private readonly egressRoutedCaps;
|
|
88
|
+
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
89
|
+
private readonly fanoutMode;
|
|
90
|
+
/** Per-child event fan-out counters (sent / suppressed). */
|
|
91
|
+
private readonly childEventStats;
|
|
92
|
+
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
93
|
+
private readonly loggedPatternSet;
|
|
72
94
|
/**
|
|
73
95
|
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
74
96
|
* or a full `LocalChildRegistryOptions` object.
|
|
@@ -82,6 +104,51 @@ export declare class LocalChildRegistry {
|
|
|
82
104
|
*/
|
|
83
105
|
constructor(serverOrOptions: LocalTransportServer | LocalChildRegistryOptions, onUnownedCallArg?: (input: CapCallInput) => Promise<unknown>);
|
|
84
106
|
start(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Per-child event fan-out counters (`{ sent, suppressed }`) or `null` if the
|
|
109
|
+
* child has no recorded events yet. Exposed for the starvation-verification
|
|
110
|
+
* surface (shadow burn-in + operator debug).
|
|
111
|
+
*/
|
|
112
|
+
getChildEventStats(childId: string): ChildEventStats | null;
|
|
113
|
+
/**
|
|
114
|
+
* Does `entry` want to receive an event in `category`? Honours the fan-out
|
|
115
|
+
* mode: `broadcast` → always; undeclared patterns (`null`) → always
|
|
116
|
+
* (fail-open); else the child's declared set must match via `matchesPattern`
|
|
117
|
+
* — the SAME function the child's own `deliverShared` map-key gate uses,
|
|
118
|
+
* giving provable delivery parity. `shadow` is handled by the caller (it
|
|
119
|
+
* counts the would-suppress but still sends).
|
|
120
|
+
*
|
|
121
|
+
* NOTE: this gate must NOT consult `RING_BUFFER_DENY_PATTERNS` /
|
|
122
|
+
* `isHighFrequencyCategory` — those are ring-buffer STORAGE policy; a child
|
|
123
|
+
* that explicitly subscribes to a ring-denied category must still RECEIVE it.
|
|
124
|
+
*/
|
|
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;
|
|
149
|
+
private statsFor;
|
|
150
|
+
/** Deduped INFO line whenever a child's declared pattern set changes. */
|
|
151
|
+
private logPatternSet;
|
|
85
152
|
/**
|
|
86
153
|
* Child id that can service a call to `capName` (optionally addressing
|
|
87
154
|
* `deviceId`), or null.
|