@camstack/system 1.2.143 → 1.2.145

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +157 -35
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +157 -35
  17. package/dist/builtins/device-manager/device-queries.d.ts +22 -0
  18. package/dist/builtins/device-manager/device-row-store.d.ts +18 -0
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  21. package/dist/builtins/hub-forwarder/index.js +1 -1
  22. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  27. package/dist/builtins/local-network/local-network.addon.js +1 -1
  28. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  29. package/dist/builtins/loki-logging/index.js +1 -1
  30. package/dist/builtins/loki-logging/index.mjs +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  33. package/dist/builtins/platform-probe/index.js +1 -1
  34. package/dist/builtins/platform-probe/index.mjs +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/snapshot/index.js +1 -1
  38. package/dist/builtins/snapshot/index.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  41. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +14 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-CQs1b7uh.js → dist-BVU5JADq.js} +488 -29
  51. package/dist/{dist-CVuVKA-O.mjs → dist-CG7kwIZp.mjs} +488 -29
  52. package/dist/index.js +28 -9
  53. package/dist/index.mjs +28 -9
  54. package/dist/kernel/index.d.ts +1 -1
  55. package/dist/kernel/moleculer/children-batch-loader.d.ts +46 -0
  56. package/dist/kernel/moleculer/core-cap-service.d.ts +17 -2
  57. package/dist/kernel/moleculer/process-service.d.ts +15 -6
  58. package/dist/{manifest-python-deps-BuhBXCg0.mjs → manifest-python-deps-BRyNCnnG.mjs} +191 -1
  59. package/dist/{manifest-python-deps-B_mCU6gz.js → manifest-python-deps-GjlyPjm0.js} +191 -1
  60. package/dist/{retired-settings-keys-DnMYSIEr.mjs → retired-settings-keys-Dm1z4mO4.mjs} +1 -1
  61. package/dist/{retired-settings-keys-Dvei9nhG.js → retired-settings-keys-_PLI9w0k.js} +1 -1
  62. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-CQs1b7uh.js");
3
+ const require_dist = require("./dist-BVU5JADq.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -26,7 +26,7 @@ require("./builtins/system-config/index.js");
26
26
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
27
27
  const require_file_data_plane = require("./file-data-plane-DO8KbxCe.js");
28
28
  const require_tls$1 = require("./tls-u8QCJCFE.js");
29
- const require_manifest_python_deps = require("./manifest-python-deps-B_mCU6gz.js");
29
+ const require_manifest_python_deps = require("./manifest-python-deps-GjlyPjm0.js");
30
30
  const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
31
31
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
32
32
  let zod = require("zod");
@@ -93435,13 +93435,23 @@ function createCoreCapService(options) {
93435
93435
  const actions = {};
93436
93436
  for (const { actionName, invoke } of options.actions) actions[actionName] = { handler: async (ctx) => {
93437
93437
  const raw = require_manifest_python_deps.deserializeTypedArrays(ctx.params);
93438
- return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw);
93438
+ return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw, readOrigin(ctx));
93439
93439
  } };
93440
93440
  return {
93441
93441
  name: options.serviceName ?? "$core-caps",
93442
93442
  actions
93443
93443
  };
93444
93444
  }
93445
+ /** Structural read of the calling node/action. Anything unrecognised is `null`. */
93446
+ function readOrigin(ctx) {
93447
+ return {
93448
+ nodeId: asName(ctx.nodeID),
93449
+ caller: asName(ctx.caller)
93450
+ };
93451
+ }
93452
+ function asName(value) {
93453
+ return typeof value === "string" && value.length > 0 ? value : null;
93454
+ }
93445
93455
  //#endregion
93446
93456
  //#region src/kernel/moleculer/hub-log-forwarder.ts
93447
93457
  /**
@@ -94339,10 +94349,20 @@ function isHeavyRunner(addons) {
94339
94349
  * one-off burst becomes the runner's permanent RSS.
94340
94350
  *
94341
94351
  * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
94342
- * there after the deferred archive walk (99,809 segments in 84s), while the `RecordingIndex` it
94343
- * actually retains is arithmetically ~21 MB (a `SegmentRow` is 5 numbers + 3 strings ≈ 207 B). The
94344
- * same addon on two agent nodes carrying the same code and a smaller archive sits at **58 MB**.
94345
- * The ~2 GB is walk garbage held as high-water in a heap that had no reason to collect it.
94352
+ * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
94353
+ * **58 MB**.
94354
+ *
94355
+ * This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
94356
+ * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
94357
+ * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
94358
+ * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
94359
+ * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
94360
+ * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
94361
+ * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
94362
+ * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
94363
+ *
94364
+ * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
94365
+ * no reason to collect" is the explanation to reach for last, not first.
94346
94366
  *
94347
94367
  * ## Why 1024
94348
94368
  *
@@ -94359,8 +94379,7 @@ function isHeavyRunner(addons) {
94359
94379
  *
94360
94380
  * A runner whose working set genuinely does not fit declares its own number in its manifest
94361
94381
  * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
94362
- * `recorder` (3072 MB; peak 2341 MB heapTotal during the deferred archive walk) is the one
94363
- * shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
94382
+ * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
94364
94383
  * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
94365
94384
  * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
94366
94385
  * from RSS.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { $ as kebabToCamel, Bt as emitDownForOwnedCaps, C as addonSettingsCapability, Dt as DATAPLANE_SECRET_HEADER$1, G as extractNestedAddonId, Ht as expandCapMethods, It as asJsonObject$1, J as isCollectionArrayMethod, Jt as readinessKey, Kt as parseJsonObject, Lt as asNumber, Nt as ReadinessRegistry, Ot as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Pt as ReadinessTimeoutError, Q as isVoidInput, Qt as EventCategory$1, Rt as asString$1, Tt as errMsg$1, Xt as scopeKey, Y as isObjectInput, Yt as resolveCapMount, dt as resolveMethodAuth, et as lifecycleJobSchema, f as LOG_LEVEL_RANK, it as looseSchema, kt as DEVICE_STATUS_METHOD, m as RUNTIME_DEFAULTS, mt as scopesAllowDeviceCap, p as METHOD_ACCESS_MAP, pt as scopesAllowAddon, q as isArrayOutputSchema, qt as parseJsonUnknown$1, rt as logLevelAtMost, st as objectInputDeclaresAddonId, t as ALL_CAPABILITY_DEFINITIONS, ut as procedureAuthKey, zt as createEvent } from "./dist-CVuVKA-O.mjs";
2
+ import { $ as kebabToCamel, Bt as emitDownForOwnedCaps, C as addonSettingsCapability, Dt as DATAPLANE_SECRET_HEADER$1, G as extractNestedAddonId, Ht as expandCapMethods, It as asJsonObject$1, J as isCollectionArrayMethod, Jt as readinessKey, Kt as parseJsonObject, Lt as asNumber, Nt as ReadinessRegistry, Ot as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Pt as ReadinessTimeoutError, Q as isVoidInput, Qt as EventCategory$1, Rt as asString$1, Tt as errMsg$1, Xt as scopeKey, Y as isObjectInput, Yt as resolveCapMount, dt as resolveMethodAuth, et as lifecycleJobSchema, f as LOG_LEVEL_RANK, it as looseSchema, kt as DEVICE_STATUS_METHOD, m as RUNTIME_DEFAULTS, mt as scopesAllowDeviceCap, p as METHOD_ACCESS_MAP, pt as scopesAllowAddon, q as isArrayOutputSchema, qt as parseJsonUnknown$1, rt as logLevelAtMost, st as objectInputDeclaresAddonId, t as ALL_CAPABILITY_DEFINITIONS, ut as procedureAuthKey, zt as createEvent } from "./dist-CG7kwIZp.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -25,7 +25,7 @@ import "./builtins/system-config/index.mjs";
25
25
  import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
26
26
  import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-BhKdxJgf.mjs";
27
27
  import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-CQhPGSJm.mjs";
28
- import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, At as createV8Reclaimer, B as AGENT_CAP_FWD_SERVICE, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as HUB_CAP_FWD_ACTION, Dt as HEAP_WATCH_WARN_RATIO, E as localProviderLink, Et as HEAP_WATCH_INTERVAL_MS, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, Mt as startHeapWatch, N as udsChildLogToWorkerEntry, Nt as startRunnerHeapWatch, O as HUB_CAP_FWD_SERVICE, Ot as RUNNER_HEAP_WATCH_INTERVAL_MS, P as createUdsLogger, Pt as strandedMb, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerCallForCap, St as runNpm, T as ipcParentLink, Tt as HEAP_RECLAIM_TRIGGER_MB, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as createKernelHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as capActionName, d as getBrokerEventBus, dt as capServiceName, et as createBrokerDeviceManagerApi, f as getMoleculerEventStats, ft as parseCapAction, g as AddonDepsManager, gt as CapabilityHandle, h as subscribePassthrough, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, jt as shouldReclaim, k as createHubCapForwardService, kt as buildHeapSample, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as setNodeEventInterest, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as registerEventBusService, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as clusterEventTopic, ut as capBareAction, v as resolveHwAccel, vt as copyBundledNativeModules, w as buildLinkChain, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as installManifestNativeDeps, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-BuhBXCg0.mjs";
28
+ import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, At as createV8Reclaimer, B as AGENT_CAP_FWD_SERVICE, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as HUB_CAP_FWD_ACTION, Dt as HEAP_WATCH_WARN_RATIO, E as localProviderLink, Et as HEAP_WATCH_INTERVAL_MS, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, Mt as startHeapWatch, N as udsChildLogToWorkerEntry, Nt as startRunnerHeapWatch, O as HUB_CAP_FWD_SERVICE, Ot as RUNNER_HEAP_WATCH_INTERVAL_MS, P as createUdsLogger, Pt as strandedMb, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerCallForCap, St as runNpm, T as ipcParentLink, Tt as HEAP_RECLAIM_TRIGGER_MB, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as createKernelHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as capActionName, d as getBrokerEventBus, dt as capServiceName, et as createBrokerDeviceManagerApi, f as getMoleculerEventStats, ft as parseCapAction, g as AddonDepsManager, gt as CapabilityHandle, h as subscribePassthrough, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, jt as shouldReclaim, k as createHubCapForwardService, kt as buildHeapSample, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as setNodeEventInterest, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as registerEventBusService, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as clusterEventTopic, ut as capBareAction, v as resolveHwAccel, vt as copyBundledNativeModules, w as buildLinkChain, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as installManifestNativeDeps, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-BRyNCnnG.mjs";
29
29
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
30
30
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
31
31
  import { z } from "zod";
@@ -93428,13 +93428,23 @@ function createCoreCapService(options) {
93428
93428
  const actions = {};
93429
93429
  for (const { actionName, invoke } of options.actions) actions[actionName] = { handler: async (ctx) => {
93430
93430
  const raw = deserializeTypedArrays(ctx.params);
93431
- return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw);
93431
+ return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw, readOrigin(ctx));
93432
93432
  } };
93433
93433
  return {
93434
93434
  name: options.serviceName ?? "$core-caps",
93435
93435
  actions
93436
93436
  };
93437
93437
  }
93438
+ /** Structural read of the calling node/action. Anything unrecognised is `null`. */
93439
+ function readOrigin(ctx) {
93440
+ return {
93441
+ nodeId: asName(ctx.nodeID),
93442
+ caller: asName(ctx.caller)
93443
+ };
93444
+ }
93445
+ function asName(value) {
93446
+ return typeof value === "string" && value.length > 0 ? value : null;
93447
+ }
93438
93448
  //#endregion
93439
93449
  //#region src/kernel/moleculer/hub-log-forwarder.ts
93440
93450
  /**
@@ -94332,10 +94342,20 @@ function isHeavyRunner(addons) {
94332
94342
  * one-off burst becomes the runner's permanent RSS.
94333
94343
  *
94334
94344
  * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
94335
- * there after the deferred archive walk (99,809 segments in 84s), while the `RecordingIndex` it
94336
- * actually retains is arithmetically ~21 MB (a `SegmentRow` is 5 numbers + 3 strings ≈ 207 B). The
94337
- * same addon on two agent nodes carrying the same code and a smaller archive sits at **58 MB**.
94338
- * The ~2 GB is walk garbage held as high-water in a heap that had no reason to collect it.
94345
+ * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
94346
+ * **58 MB**.
94347
+ *
94348
+ * This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
94349
+ * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
94350
+ * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
94351
+ * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
94352
+ * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
94353
+ * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
94354
+ * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
94355
+ * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
94356
+ *
94357
+ * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
94358
+ * no reason to collect" is the explanation to reach for last, not first.
94339
94359
  *
94340
94360
  * ## Why 1024
94341
94361
  *
@@ -94352,8 +94372,7 @@ function isHeavyRunner(addons) {
94352
94372
  *
94353
94373
  * A runner whose working set genuinely does not fit declares its own number in its manifest
94354
94374
  * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
94355
- * `recorder` (3072 MB; peak 2341 MB heapTotal during the deferred archive walk) is the one
94356
- * shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
94375
+ * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
94357
94376
  * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
94358
94377
  * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
94359
94378
  * from RSS.
@@ -37,7 +37,7 @@ export type { BrokerConfig } from './moleculer/broker-factory.js';
37
37
  export { type AddonDeploySource, isAddonDeploySource } from './moleculer/addon-deploy-source.js';
38
38
  export { createAddonService, validateProviderRegistrations, } from './moleculer/addon-service-factory.js';
39
39
  export { createCoreCapService, CORE_CAP_SERVICE_NAME } from './moleculer/core-cap-service.js';
40
- export type { CoreCapAction, CoreCapServiceOptions } from './moleculer/core-cap-service.js';
40
+ export type { CoreCapAction, CoreCapCallOrigin, CoreCapServiceOptions, } from './moleculer/core-cap-service.js';
41
41
  export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, registerEventBusService, setNodeEventInterest, getMoleculerEventStats, subscribePassthrough, getBrokerEventBus, clusterEventTopic, EVENT_TOPIC_PREFIX, } from './moleculer/addon-context-factory.js';
42
42
  export type { MoleculerEventStats } from './moleculer/addon-context-factory.js';
43
43
  export { HubLogForwarder } from './moleculer/hub-log-forwarder.js';
@@ -0,0 +1,46 @@
1
+ import { IScopedLogger } from '@camstack/types';
2
+ /**
3
+ * The child fields the accessory reconcile reads. Deliberately narrower than
4
+ * `DeviceInfo`: the loader is a transport, and a transport that pins the whole
5
+ * projection would have to change every time an unrelated field does.
6
+ */
7
+ export interface ReconcileChildRow {
8
+ readonly id: number;
9
+ readonly stableId: string;
10
+ readonly name: string;
11
+ readonly linkDeviceId?: number | null;
12
+ }
13
+ /**
14
+ * Narrow an untyped cap answer to the fields the reconcile reads.
15
+ *
16
+ * `callDeviceManager` is an untyped boundary, and the answer may come from a
17
+ * hub of a different vintage. A row that does not carry an id, a stableId and
18
+ * a name is not a child this code can prune ON, so it is dropped rather than
19
+ * cast into shape — the prune step DELETES, and a half-parsed row is the input
20
+ * that would make it delete the wrong thing.
21
+ */
22
+ export declare function parseChildRows(value: unknown): readonly ReconcileChildRow[] | null;
23
+ /**
24
+ * Narrow a `getChildrenBatch` answer: a record keyed by parent id as a string.
25
+ *
26
+ * An EMPTY record is a valid answer — "none of these parents has children" —
27
+ * and so is what a hub that answers the call but knows none of the parents
28
+ * would send. Both mean "nothing to prune", which is the safe direction. Only
29
+ * a shape that is not a record at all is `null`, and the loader treats that as
30
+ * a failed batch and re-asks per parent.
31
+ */
32
+ export declare function parseChildrenByParent(value: unknown): ReadonlyMap<number, readonly ReconcileChildRow[]> | null;
33
+ export interface ChildrenBatchLoaderOptions {
34
+ /** One `device-manager.getChildrenBatch` call. Rejects like any RPC. */
35
+ readonly fetchBatch: (parentDeviceIds: readonly number[]) => Promise<ReadonlyMap<number, readonly ReconcileChildRow[]>>;
36
+ /** One `device-manager.getChildren` call — the fallback, and the old path. */
37
+ readonly fetchOne: (parentDeviceId: number) => Promise<readonly ReconcileChildRow[]>;
38
+ readonly logger: IScopedLogger;
39
+ /** Test seam. Production takes {@link DEVICE_CHILDREN_BATCH_MAX}. */
40
+ readonly maxBatch?: number;
41
+ }
42
+ export interface ChildrenBatchLoader {
43
+ /** The direct children of one parent, coalesced with every other in-flight ask. */
44
+ load(parentDeviceId: number): Promise<readonly ReconcileChildRow[]>;
45
+ }
46
+ export declare function createChildrenBatchLoader(opts: ChildrenBatchLoaderOptions): ChildrenBatchLoader;
@@ -1,4 +1,13 @@
1
1
  import { ServiceSchema } from 'moleculer';
2
+ /**
3
+ * Who issued a `$core-caps` action. Both fields are `null` when Moleculer did
4
+ * not carry the information — never blank, so an absent origin reads as absent
5
+ * rather than as a formatting defect.
6
+ */
7
+ export interface CoreCapCallOrigin {
8
+ readonly nodeId: string | null;
9
+ readonly caller: string | null;
10
+ }
2
11
  /**
3
12
  * Default Moleculer service name for the hub core-capability bridge.
4
13
  *
@@ -22,8 +31,14 @@ export declare const CORE_CAP_SERVICE_NAME = "$core-caps";
22
31
  export interface CoreCapAction {
23
32
  /** Kebab-cased `<capName>.<method>` — e.g. `system.info`, `addon-settings-raw.getGlobal`. */
24
33
  readonly actionName: string;
25
- /** Invoke the underlying tRPC procedure with a trusted mesh context. */
26
- readonly invoke: (input: unknown) => Promise<unknown> | unknown;
34
+ /**
35
+ * Invoke the underlying tRPC procedure with a trusted mesh context.
36
+ *
37
+ * `origin` is diagnostics only — the transport census uses it to name the
38
+ * caller of an in-process call, which has no request and therefore no other
39
+ * identity. No implementation may gate on it.
40
+ */
41
+ readonly invoke: (input: unknown, origin: CoreCapCallOrigin) => Promise<unknown> | unknown;
27
42
  }
28
43
  export interface CoreCapServiceOptions {
29
44
  /** Moleculer service name. Defaults to {@link CORE_CAP_SERVICE_NAME}. */
@@ -16,10 +16,20 @@ interface RunnerAddonSpec {
16
16
  * one-off burst becomes the runner's permanent RSS.
17
17
  *
18
18
  * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
19
- * there after the deferred archive walk (99,809 segments in 84s), while the `RecordingIndex` it
20
- * actually retains is arithmetically ~21 MB (a `SegmentRow` is 5 numbers + 3 strings ≈ 207 B). The
21
- * same addon on two agent nodes carrying the same code and a smaller archive sits at **58 MB**.
22
- * The ~2 GB is walk garbage held as high-water in a heap that had no reason to collect it.
19
+ * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
20
+ * **58 MB**.
21
+ *
22
+ * This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
23
+ * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
24
+ * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
25
+ * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
26
+ * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
27
+ * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
28
+ * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
29
+ * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
30
+ *
31
+ * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
32
+ * no reason to collect" is the explanation to reach for last, not first.
23
33
  *
24
34
  * ## Why 1024
25
35
  *
@@ -36,8 +46,7 @@ interface RunnerAddonSpec {
36
46
  *
37
47
  * A runner whose working set genuinely does not fit declares its own number in its manifest
38
48
  * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
39
- * `recorder` (3072 MB; peak 2341 MB heapTotal during the deferred archive walk) is the one
40
- * shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
49
+ * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
41
50
  * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
42
51
  * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
43
52
  * from RSS.
@@ -1,3 +1,4 @@
1
+ import "./dist-CG7kwIZp.mjs";
1
2
  import * as crypto$1 from "node:crypto";
2
3
  import { randomBytes, randomUUID } from "node:crypto";
3
4
  import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
@@ -1649,6 +1650,182 @@ async function readModifyWrite(input) {
1649
1650
  return true;
1650
1651
  }
1651
1652
  //#endregion
1653
+ //#region src/kernel/moleculer/children-batch-loader.ts
1654
+ /**
1655
+ * "Who are my children?" asked by a thousand devices, answered in a handful of
1656
+ * calls.
1657
+ *
1658
+ * ## The number
1659
+ *
1660
+ * Every `BaseDevice` inherits a `getAccessoryChildren()` that returns `[]`, so
1661
+ * the accessory reconcile in `device-cap-proxy.ts` runs its orphan-prune step
1662
+ * for EVERY registered device — including the leaf accessories that can never
1663
+ * own one. Each pass asked `device-manager.getChildren` for its own id.
1664
+ * Measured on the live hub 2026-08-27, 120-second boot window, 1 017 devices:
1665
+ * **1 024 calls returning 919 rows in total**. A thousand round-trips, and a
1666
+ * thousand indexed scans behind them, to move less than one row each.
1667
+ *
1668
+ * ## Why a loader and not a caller that loops
1669
+ *
1670
+ * There is no loop to unroll. Reconcile is per-device by construction: it runs
1671
+ * inside `register()`, and again whenever a late feature-probe writes the
1672
+ * slice. The repetition is a THOUSAND INDEPENDENT CALLERS, not one caller
1673
+ * asking a thousand times — so the collapse has to happen between them.
1674
+ *
1675
+ * ## Why the batch is self-clocking, with no window to tune
1676
+ *
1677
+ * At most ONE batch call is in flight. Anything that arrives while it is in
1678
+ * flight lands in the next batch, dispatched the moment the current one
1679
+ * settles. That makes the batch size adapt to the arrival rate on its own:
1680
+ *
1681
+ * - Idle: the first caller flushes on the next macrotask. No added wait, and
1682
+ * no timer to pick a value for and be wrong about.
1683
+ * - Boot: `BaseDeviceProvider.onRestoreDevices` issues every top-level
1684
+ * `create()` at once and `runBounded(specs, 12)` spawns accessory children
1685
+ * twelve wide, so reconciles arrive in bursts — measured as 2 372 device
1686
+ * log lines in one 5-second bucket at boot+16s, against 62 at boot+142s.
1687
+ * Every arrival inside one round-trip collapses into the next call.
1688
+ * - Worst case: arrivals slower than a round-trip give batches of one, which
1689
+ * is exactly today's cost. The loader can never be worse than what it
1690
+ * replaces.
1691
+ *
1692
+ * A fixed debounce window would have to be guessed against that distribution,
1693
+ * and would tax the idle case to pay for the busy one.
1694
+ *
1695
+ * ## Degrading, rather than breaking, against an older hub
1696
+ *
1697
+ * `getChildrenBatch` is a NEW method: a hub that predates it rejects the call
1698
+ * (it does not silently strip the input and answer a different question, which
1699
+ * is what adding a `parentDeviceIds` FIELD to `getChildren` would have done).
1700
+ * Every waiter in a failed batch is then answered by the per-parent
1701
+ * `getChildren` it would have used anyway, so the reconcile is never denied
1702
+ * its answer. A rejection that names the method as unknown latches the
1703
+ * fallback permanently — re-probing a method the hub will never have, once per
1704
+ * batch for a whole boot, is the storm this exists to remove.
1705
+ */
1706
+ /**
1707
+ * Narrow an untyped cap answer to the fields the reconcile reads.
1708
+ *
1709
+ * `callDeviceManager` is an untyped boundary, and the answer may come from a
1710
+ * hub of a different vintage. A row that does not carry an id, a stableId and
1711
+ * a name is not a child this code can prune ON, so it is dropped rather than
1712
+ * cast into shape — the prune step DELETES, and a half-parsed row is the input
1713
+ * that would make it delete the wrong thing.
1714
+ */
1715
+ function parseChildRows(value) {
1716
+ if (!Array.isArray(value)) return null;
1717
+ const rows = [];
1718
+ for (const entry of value) {
1719
+ if (typeof entry !== "object" || entry === null) return null;
1720
+ const id = Reflect.get(entry, "id");
1721
+ const stableId = Reflect.get(entry, "stableId");
1722
+ const name = Reflect.get(entry, "name");
1723
+ const linkDeviceId = Reflect.get(entry, "linkDeviceId");
1724
+ if (typeof id !== "number" || typeof stableId !== "string" || typeof name !== "string") return null;
1725
+ rows.push({
1726
+ id,
1727
+ stableId,
1728
+ name,
1729
+ ...typeof linkDeviceId === "number" || linkDeviceId === null ? { linkDeviceId } : {}
1730
+ });
1731
+ }
1732
+ return rows;
1733
+ }
1734
+ /**
1735
+ * Narrow a `getChildrenBatch` answer: a record keyed by parent id as a string.
1736
+ *
1737
+ * An EMPTY record is a valid answer — "none of these parents has children" —
1738
+ * and so is what a hub that answers the call but knows none of the parents
1739
+ * would send. Both mean "nothing to prune", which is the safe direction. Only
1740
+ * a shape that is not a record at all is `null`, and the loader treats that as
1741
+ * a failed batch and re-asks per parent.
1742
+ */
1743
+ function parseChildrenByParent(value) {
1744
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
1745
+ const out = /* @__PURE__ */ new Map();
1746
+ for (const [key, entry] of Object.entries(value)) {
1747
+ const parentDeviceId = Number(key);
1748
+ if (!Number.isInteger(parentDeviceId)) return null;
1749
+ const rows = parseChildRows(entry);
1750
+ if (rows === null) return null;
1751
+ out.set(parentDeviceId, rows);
1752
+ }
1753
+ return out;
1754
+ }
1755
+ /**
1756
+ * True when an error says the hub has no such procedure — the version-skew
1757
+ * case, and the only one worth latching on. Anything else (a timeout, a busy
1758
+ * broker) is transient and must not disable batching for the process lifetime.
1759
+ */
1760
+ function looksLikeUnknownMethod(err) {
1761
+ const lowered = (err instanceof Error ? err.message : String(err)).toLowerCase();
1762
+ return lowered.includes("not_found") || lowered.includes("no procedure") || lowered.includes("unknown method") || lowered.includes("no such method") || lowered.includes("not available");
1763
+ }
1764
+ function createChildrenBatchLoader(opts) {
1765
+ const maxBatch = Math.max(1, opts.maxBatch ?? 256);
1766
+ let pending = [];
1767
+ let flushScheduled = false;
1768
+ let flushing = false;
1769
+ let batchUnsupported = false;
1770
+ const answerOneByOne = async (waiters) => {
1771
+ await Promise.all(waiters.map(async (waiter) => {
1772
+ try {
1773
+ waiter.resolve(await opts.fetchOne(waiter.parentDeviceId));
1774
+ } catch (err) {
1775
+ waiter.reject(err);
1776
+ }
1777
+ }));
1778
+ };
1779
+ const runBatch = async (waiters) => {
1780
+ const parentDeviceIds = [...new Set(waiters.map((w) => w.parentDeviceId))];
1781
+ try {
1782
+ const byParent = await opts.fetchBatch(parentDeviceIds);
1783
+ for (const waiter of waiters) waiter.resolve(byParent.get(waiter.parentDeviceId) ?? []);
1784
+ } catch (err) {
1785
+ const unknown = looksLikeUnknownMethod(err);
1786
+ if (unknown) batchUnsupported = true;
1787
+ opts.logger.warn(unknown ? "device-manager.getChildrenBatch is not available on this hub — falling back to per-parent getChildren for the process lifetime" : "device-manager.getChildrenBatch failed — answering this batch per parent", { meta: {
1788
+ parents: parentDeviceIds.length,
1789
+ error: err instanceof Error ? err.message : String(err)
1790
+ } });
1791
+ await answerOneByOne(waiters);
1792
+ }
1793
+ };
1794
+ const flush = async () => {
1795
+ flushing = true;
1796
+ try {
1797
+ while (pending.length > 0) {
1798
+ const batch = pending.slice(0, maxBatch);
1799
+ pending = pending.slice(maxBatch);
1800
+ if (batchUnsupported) await answerOneByOne(batch);
1801
+ else await runBatch(batch);
1802
+ }
1803
+ } finally {
1804
+ flushing = false;
1805
+ }
1806
+ };
1807
+ const scheduleFlush = () => {
1808
+ if (flushScheduled || flushing) return;
1809
+ flushScheduled = true;
1810
+ setTimeout(() => {
1811
+ flushScheduled = false;
1812
+ flush().catch((err) => {
1813
+ opts.logger.warn("children batch flush threw", { meta: { error: err instanceof Error ? err.message : String(err) } });
1814
+ });
1815
+ }, 0);
1816
+ };
1817
+ return { load(parentDeviceId) {
1818
+ return new Promise((resolve, reject) => {
1819
+ pending.push({
1820
+ parentDeviceId,
1821
+ resolve,
1822
+ reject
1823
+ });
1824
+ scheduleFlush();
1825
+ });
1826
+ } };
1827
+ }
1828
+ //#endregion
1652
1829
  //#region src/kernel/moleculer/device-cap-proxy.ts
1653
1830
  /**
1654
1831
  * Max number of accessory children spawned concurrently during a single
@@ -1939,6 +2116,19 @@ function createBrokerDeviceManagerApi(opts) {
1939
2116
  api
1940
2117
  });
1941
2118
  let selfApi;
2119
+ const childrenLoader = createChildrenBatchLoader({
2120
+ logger: opts.logger,
2121
+ fetchBatch: async (parentDeviceIds) => {
2122
+ const parsed = parseChildrenByParent(await callDeviceManager(api, "getChildrenBatch", { parentDeviceIds }));
2123
+ if (parsed === null) throw new Error("getChildrenBatch returned a shape this kernel cannot read");
2124
+ return parsed;
2125
+ },
2126
+ fetchOne: async (parentDeviceId) => {
2127
+ const parsed = parseChildRows(await callDeviceManager(api, "getChildren", { parentDeviceId }));
2128
+ if (parsed === null) throw new Error("getChildren returned a shape this kernel cannot read");
2129
+ return parsed;
2130
+ }
2131
+ });
1942
2132
  const deviceRebuildFactories = /* @__PURE__ */ new Map();
1943
2133
  const buildContext = (stableId, id, parentDeviceId = null, initialRuntimeState = {}, persistedConfig = {}, deviceMeta = null) => {
1944
2134
  let runtimeStateRef = null;
@@ -2356,7 +2546,7 @@ function createBrokerDeviceManagerApi(opts) {
2356
2546
  return;
2357
2547
  }
2358
2548
  try {
2359
- const currentChildren = await callDeviceManager(api, "getChildren", { parentDeviceId: device.id });
2549
+ const currentChildren = await childrenLoader.load(device.id);
2360
2550
  for (const child of currentChildren) {
2361
2551
  if (child.linkDeviceId !== device.id) continue;
2362
2552
  if (expected.has(child.stableId)) continue;