@camstack/system 1.2.218 → 1.2.219
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 +2 -2
- package/dist/addon-runner.mjs +2 -2
- package/dist/addon-utils.js +2 -2
- package/dist/addon-utils.mjs +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/gpu-probe.d.ts +18 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +26 -3
- package/dist/builtins/native-metrics/native-metrics.addon.js +1320 -1093
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1320 -1096
- package/dist/builtins/native-metrics/proc-process-table.d.ts +25 -0
- package/dist/builtins/platform-probe/index.js +3 -3
- package/dist/builtins/platform-probe/index.mjs +3 -3
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-pragmas.d.ts +23 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +1 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/sqlite-storage/wal-checkpoint-policy.d.ts +115 -0
- package/dist/builtins/sqlite-storage/wal-checkpoint-worker.d.ts +1 -0
- package/dist/builtins/sqlite-storage/wal-checkpointer.d.ts +70 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{custom-action-registry-CsONUlhC.mjs → custom-action-registry-B6gOtUWA.mjs} +1 -1
- package/dist/{custom-action-registry-BDwM5qAK.js → custom-action-registry-DKWhaWL1.js} +1 -1
- package/dist/{dist-BOlMCOeT.js → dist-B17W9ngu.js} +2 -2
- package/dist/{dist-DigmGsQS.mjs → dist-Cyz5_i7z.mjs} +2 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +31 -36
- package/dist/index.mjs +23 -38
- package/dist/kernel/heap-watch.d.ts +24 -1
- package/dist/kernel/moleculer/mesh-queue-report.d.ts +51 -0
- package/dist/kernel/outbound-queue-watch.d.ts +94 -0
- package/dist/kernel/socket-plane-report.d.ts +75 -4
- package/dist/kernel/transport/cap-routing-hints.d.ts +21 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +28 -2
- package/dist/kernel/transport/frame-codec.d.ts +49 -0
- package/dist/kernel/transport/local-child-client.d.ts +7 -0
- package/dist/kernel/transport/local-child-registry.d.ts +36 -1
- package/dist/kernel/transport/local-transport.d.ts +45 -2
- package/dist/kernel/transport/socket-channel.d.ts +19 -1
- package/dist/{manifest-system-deps-D--wyMD9.mjs → manifest-system-deps-C7dihwWT.mjs} +1210 -682
- package/dist/{manifest-system-deps-Bo92lg5u.js → manifest-system-deps-g_nK0xsF.js} +1252 -664
- package/dist/process/proc-stat.d.ts +75 -0
- package/dist/process/resource-monitor.d.ts +9 -1
- package/dist/{resource-monitor-CdnzxBLP.js → resource-monitor-LVE1BoLs.js} +42 -23
- package/dist/{resource-monitor-BWmQ5i-o.mjs → resource-monitor-Vp0bPV3U.mjs} +42 -22
- package/dist/{retired-settings-keys-vK_U-CG2.mjs → retired-settings-keys-BCYUrdsA.mjs} +1 -1
- package/dist/{retired-settings-keys-xEAlYnv7.js → retired-settings-keys-CjvB7Etx.js} +1 -1
- package/dist/wal-checkpoint-policy-CAcg63o-.mjs +92 -0
- package/dist/wal-checkpoint-policy-CCJZngds.js +127 -0
- package/dist/wal-checkpoint-worker.js +138 -0
- package/dist/wal-checkpoint-worker.mjs +137 -0
- package/package.json +1 -1
- package/dist/{event-loop-stall-monitor-DM9OAzy8.js → event-loop-stall-monitor-CAsHdMwP.js} +1 -1
- package/dist/{event-loop-stall-monitor-B3CFd9tM.mjs → event-loop-stall-monitor-GPrVvx14.mjs} +1 -1
- package/dist/{tls-DOTmtLCW.mjs → tls-2xbgg78V.mjs} +2 -2
- package/dist/{tls-BxQlomxd.js → tls-BFs4PzoW.js} +2 -2
|
@@ -5,12 +5,12 @@ import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, instal
|
|
|
5
5
|
import { unlink } from "node:fs/promises";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
7
7
|
import { isAbsolute, join } from "node:path";
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
|
-
import { promisify } from "node:util";
|
|
10
8
|
import * as fs from "node:fs";
|
|
11
9
|
import { existsSync } from "node:fs";
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
12
11
|
import * as os from "node:os";
|
|
13
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
14
|
import { createServer } from "node:http";
|
|
15
15
|
import * as v8 from "node:v8";
|
|
16
16
|
import { Session } from "node:inspector/promises";
|
|
@@ -308,12 +308,15 @@ function createSocketPlaneReader(sources) {
|
|
|
308
308
|
peers.push({
|
|
309
309
|
peerId: child.childId,
|
|
310
310
|
tx: traffic.tx,
|
|
311
|
-
rx: traffic.rx
|
|
311
|
+
rx: traffic.rx,
|
|
312
|
+
queuedBytes: registry.getChildQueuedBytes(child.childId)
|
|
312
313
|
});
|
|
313
314
|
}
|
|
315
|
+
const forward = registry.readForwardCensus?.();
|
|
314
316
|
return {
|
|
315
317
|
peers,
|
|
316
|
-
peersConnected: listed.length
|
|
318
|
+
peersConnected: listed.length,
|
|
319
|
+
...forward === void 0 ? {} : { forward }
|
|
317
320
|
};
|
|
318
321
|
} catch {
|
|
319
322
|
return;
|
|
@@ -331,8 +334,41 @@ function createSocketPlaneReader(sources) {
|
|
|
331
334
|
* stops being read.
|
|
332
335
|
*/
|
|
333
336
|
var SOCKET_PLANE_TOP_N = 5;
|
|
337
|
+
/** How many queue-holding peers get named. Three: a queue is an exception,
|
|
338
|
+
* and the line already carries five names for traffic. */
|
|
339
|
+
var SOCKET_QUEUE_TOP_N = 3;
|
|
340
|
+
/** The queue reading over the current counters — a level, so no baseline. */
|
|
341
|
+
function readSocketQueues(current) {
|
|
342
|
+
let totalBytes = 0;
|
|
343
|
+
let unmeasured = 0;
|
|
344
|
+
const holding = [];
|
|
345
|
+
for (const peer of current.peers) {
|
|
346
|
+
if (peer.queuedBytes === null) {
|
|
347
|
+
unmeasured += 1;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
totalBytes += peer.queuedBytes;
|
|
351
|
+
if (peer.queuedBytes > 0) holding.push({
|
|
352
|
+
peerId: peer.peerId,
|
|
353
|
+
bytes: peer.queuedBytes
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
totalBytes,
|
|
358
|
+
unmeasured,
|
|
359
|
+
top: holding.toSorted((a, b) => b.bytes - a.bytes || a.peerId.localeCompare(b.peerId)).slice(0, 3)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
334
362
|
/** A process that has never read the plane. */
|
|
335
363
|
var EMPTY_SOCKET_PLANE_BASELINE = { peers: /* @__PURE__ */ new Map() };
|
|
364
|
+
/** The census over the window; a counter that went backwards reads as quiet. */
|
|
365
|
+
function diffForward(current, before) {
|
|
366
|
+
if (current === void 0) return void 0;
|
|
367
|
+
return {
|
|
368
|
+
raw: Math.max(0, current.raw - (before?.raw ?? 0)),
|
|
369
|
+
decoded: Math.max(0, current.decoded - (before?.decoded ?? 0))
|
|
370
|
+
};
|
|
371
|
+
}
|
|
336
372
|
function diffDirection(current, before) {
|
|
337
373
|
const previous = before ?? EMPTY_SOCKET_DIRECTION;
|
|
338
374
|
return {
|
|
@@ -358,6 +394,14 @@ function addDirection(into, add) {
|
|
|
358
394
|
function peerMessages(delta) {
|
|
359
395
|
return socketDirectionMessages(delta.tx) + socketDirectionMessages(delta.rx);
|
|
360
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Total bytes a peer moved in the window, both directions — the OTHER
|
|
399
|
+
* ranking, and the one that names a peer the message ranking structurally
|
|
400
|
+
* cannot see (D454).
|
|
401
|
+
*/
|
|
402
|
+
function peerBytes(delta) {
|
|
403
|
+
return socketDirectionBytes(delta.tx) + socketDirectionBytes(delta.rx);
|
|
404
|
+
}
|
|
361
405
|
/** Subtract the previous cumulative reading from the current one. */
|
|
362
406
|
function diffSocketPlane(baseline, current, topN = 5) {
|
|
363
407
|
const deltas = [];
|
|
@@ -374,12 +418,16 @@ function diffSocketPlane(baseline, current, topN = 5) {
|
|
|
374
418
|
rx = addDirection(rx, delta.rx);
|
|
375
419
|
if (peerMessages(delta) > 0) deltas.push(delta);
|
|
376
420
|
}
|
|
421
|
+
const forward = diffForward(current.forward, baseline.forward);
|
|
377
422
|
return {
|
|
378
423
|
peersConnected: current.peersConnected,
|
|
379
424
|
peersMeasured: current.peers.length,
|
|
380
425
|
tx,
|
|
381
426
|
rx,
|
|
382
|
-
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN)
|
|
427
|
+
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN),
|
|
428
|
+
topBytes: deltas.toSorted((a, b) => peerBytes(b) - peerBytes(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN),
|
|
429
|
+
queued: readSocketQueues(current),
|
|
430
|
+
...forward === void 0 ? {} : { forward }
|
|
383
431
|
};
|
|
384
432
|
}
|
|
385
433
|
function createSocketPlaneMeter(reader, topN = 5) {
|
|
@@ -388,24 +436,43 @@ function createSocketPlaneMeter(reader, topN = 5) {
|
|
|
388
436
|
const current = reader();
|
|
389
437
|
if (current === void 0) return void 0;
|
|
390
438
|
const window = diffSocketPlane(baseline, current, topN);
|
|
391
|
-
baseline = {
|
|
439
|
+
baseline = {
|
|
440
|
+
peers: new Map(current.peers.map((p) => [p.peerId, p])),
|
|
441
|
+
...current.forward === void 0 ? {} : { forward: current.forward }
|
|
442
|
+
};
|
|
392
443
|
return window;
|
|
393
444
|
} };
|
|
394
445
|
}
|
|
395
446
|
/** Bytes → whole kB. The plane moves 180–330 MB/min; raw bytes would put
|
|
396
447
|
* nine-digit numbers on a line that has to stay readable. */
|
|
397
|
-
function kb(bytes) {
|
|
448
|
+
function kb$1(bytes) {
|
|
398
449
|
return Math.round(bytes / 1024);
|
|
399
450
|
}
|
|
400
451
|
/** `req:<msgs>/<kB>,res:<msgs>/<kB>,evt:<msgs>/<kB>` — every kind, always,
|
|
401
452
|
* including at zero: a kind that went quiet is a reading, not a blank. */
|
|
402
453
|
function formatDirection(sample) {
|
|
403
|
-
return `req:${sample.reqMessages}/${kb(sample.reqBytes)}kB,res:${sample.resMessages}/${kb(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb(sample.evtBytes)}kB`;
|
|
454
|
+
return `req:${sample.reqMessages}/${kb$1(sample.reqBytes)}kB,res:${sample.resMessages}/${kb$1(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb$1(sample.evtBytes)}kB`;
|
|
404
455
|
}
|
|
405
|
-
/** `<
|
|
406
|
-
*
|
|
456
|
+
/** `<req>/<res>/<evt>` in kB — the byte triple, in the same order as the
|
|
457
|
+
* message triple next to it, so the two read as one table. */
|
|
458
|
+
function formatPeerBytes(sample) {
|
|
459
|
+
return `${kb$1(sample.reqBytes)}/${kb$1(sample.resBytes)}/${kb$1(sample.evtBytes)}`;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* `<peer>:tx=<req>/<res>/<evt>:rx=…:txkB=<req>/<res>/<evt>:rxkB=…` — the whole
|
|
463
|
+
* peer×kind cross for one peer, messages and bytes, which is the question in
|
|
464
|
+
* one token.
|
|
465
|
+
*
|
|
466
|
+
* The bytes were a per-direction TOTAL until D454, and the total could not
|
|
467
|
+
* answer the question `socket-traffic.ts` says this instrument exists to
|
|
468
|
+
* settle: whether a peer's bytes are RESPONSES (media, and D9/D18 re-opens) or
|
|
469
|
+
* fan-out (`evt`, which scales with runner count and nothing an operator
|
|
470
|
+
* asked for). On 2026-09-11 hub-main wrote 118 MB in one minute to
|
|
471
|
+
* `hub/pipeline-analytics` and the line could only say "118 MB" — separating
|
|
472
|
+
* `res` from `evt` took an hour of arithmetic against the plane-wide totals.
|
|
473
|
+
*/
|
|
407
474
|
function formatPeer(delta) {
|
|
408
|
-
return `${delta.peerId}:tx=${delta.tx.reqMessages}/${delta.tx.resMessages}/${delta.tx.evtMessages}:rx=${delta.rx.reqMessages}/${delta.rx.resMessages}/${delta.rx.evtMessages}:
|
|
475
|
+
return `${delta.peerId}:tx=${delta.tx.reqMessages}/${delta.tx.resMessages}/${delta.tx.evtMessages}:rx=${delta.rx.reqMessages}/${delta.rx.resMessages}/${delta.rx.evtMessages}:txkB=${formatPeerBytes(delta.tx)}:rxkB=${formatPeerBytes(delta.rx)}`;
|
|
409
476
|
}
|
|
410
477
|
/**
|
|
411
478
|
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
@@ -418,14 +485,234 @@ function formatPeer(delta) {
|
|
|
418
485
|
* is the only way to see an unmeasured channel, and the per-kind splits because
|
|
419
486
|
* "the plane went quiet for a minute" is a reading.
|
|
420
487
|
*
|
|
421
|
-
* `socketTop` is
|
|
422
|
-
*
|
|
423
|
-
*
|
|
488
|
+
* `socketTop` is a conditional field — a ranked list of nothing is noise — and
|
|
489
|
+
* it is a single comma-separated value so the field COUNT does not change from
|
|
490
|
+
* line to line.
|
|
491
|
+
*
|
|
492
|
+
* `socketTopBytes` is the other one, and its PRESENCE is the reading: it is
|
|
493
|
+
* printed only when ranking by bytes names a peer that ranking by messages
|
|
494
|
+
* does not. A line without it says the two rankings agree; a line with it says
|
|
495
|
+
* a peer is moving few, heavy frames and names it (D454).
|
|
424
496
|
*/
|
|
425
497
|
function formatSocketPlane(window) {
|
|
426
498
|
if (window === void 0) return "";
|
|
427
499
|
const top = window.top.map(formatPeer).join(",");
|
|
428
|
-
|
|
500
|
+
const named = new Set(window.top.map((p) => p.peerId));
|
|
501
|
+
const topBytes = window.topBytes.filter((p) => !named.has(p.peerId)).map(formatPeer).join(",");
|
|
502
|
+
const queuedTop = window.queued.top.map((p) => `${p.peerId}:${kb$1(p.bytes)}`).join(",");
|
|
503
|
+
return ` socketPeers=${window.peersMeasured}/${window.peersConnected} socketTx=${formatDirection(window.tx)} socketRx=${formatDirection(window.rx)}` + (top.length === 0 ? "" : ` socketTop=${top}`) + (topBytes.length === 0 ? "" : ` socketTopBytes=${topBytes}`) + ` socketQueued=${kb$1(window.queued.totalBytes)}kB` + (window.queued.unmeasured === 0 ? "" : ` socketQueuedUnmeasured=${window.queued.unmeasured}`) + (queuedTop.length === 0 ? "" : ` socketQueuedTop=${queuedTop}`) + (window.forward === void 0 ? "" : ` socketFwd=${window.forward.raw}/${window.forward.decoded}`);
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/kernel/moleculer/mesh-queue-report.ts
|
|
507
|
+
function isRecord$1(value) {
|
|
508
|
+
return typeof value === "object" && value !== null;
|
|
509
|
+
}
|
|
510
|
+
/** The shape `TcpWriter` keeps its sockets in. Anything else is not a mesh. */
|
|
511
|
+
function writerSocketsOf(broker) {
|
|
512
|
+
if (!isRecord$1(broker)) return void 0;
|
|
513
|
+
const transit = broker["transit"];
|
|
514
|
+
if (!isRecord$1(transit)) return void 0;
|
|
515
|
+
const transporter = transit["tx"];
|
|
516
|
+
if (!isRecord$1(transporter)) return void 0;
|
|
517
|
+
const writer = transporter["writer"];
|
|
518
|
+
if (!isRecord$1(writer)) return void 0;
|
|
519
|
+
const sockets = writer["sockets"];
|
|
520
|
+
return sockets instanceof Map ? sockets : void 0;
|
|
521
|
+
}
|
|
522
|
+
function readSocket(nodeId, socket) {
|
|
523
|
+
if (typeof nodeId !== "string" || !isRecord$1(socket)) return void 0;
|
|
524
|
+
const queuedBytes = socket["writableLength"];
|
|
525
|
+
const bytesWritten = socket["bytesWritten"];
|
|
526
|
+
if (typeof queuedBytes !== "number" || typeof bytesWritten !== "number") return void 0;
|
|
527
|
+
return {
|
|
528
|
+
nodeId,
|
|
529
|
+
queuedBytes,
|
|
530
|
+
bytesWritten
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Build a reader over a lazily-resolved broker. Wrapped: a broker mid-teardown
|
|
535
|
+
* may throw, and a diagnostic that can crash the process it watches is worse
|
|
536
|
+
* than none.
|
|
537
|
+
*/
|
|
538
|
+
function createMeshQueueReader(getBroker) {
|
|
539
|
+
return () => {
|
|
540
|
+
try {
|
|
541
|
+
const sockets = writerSocketsOf(getBroker());
|
|
542
|
+
if (sockets === void 0) return void 0;
|
|
543
|
+
const peers = [];
|
|
544
|
+
for (const [nodeId, socket] of sockets) {
|
|
545
|
+
const peer = readSocket(nodeId, socket);
|
|
546
|
+
if (peer !== void 0) peers.push(peer);
|
|
547
|
+
}
|
|
548
|
+
return peers;
|
|
549
|
+
} catch {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/** At most this many nodes named. A cluster has a handful. */
|
|
555
|
+
var MESH_QUEUE_TOP_N = 3;
|
|
556
|
+
function kb(bytes) {
|
|
557
|
+
return Math.round(bytes / 1024);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
561
|
+
* this process has no mesh writer. `meshQueued` is printed always, at zero
|
|
562
|
+
* included: a quiet mesh is a reading. `meshQueuedTop` names only nodes with
|
|
563
|
+
* a queue, largest first — a ranked list of zeros is noise.
|
|
564
|
+
*/
|
|
565
|
+
function formatMeshQueue(peers) {
|
|
566
|
+
if (peers === void 0) return "";
|
|
567
|
+
const total = peers.reduce((sum, p) => sum + p.queuedBytes, 0);
|
|
568
|
+
const top = peers.filter((p) => p.queuedBytes > 0).toSorted((a, b) => b.queuedBytes - a.queuedBytes || a.nodeId.localeCompare(b.nodeId)).slice(0, 3).map((p) => `${p.nodeId}:${kb(p.queuedBytes)}`).join(",");
|
|
569
|
+
return ` meshQueued=${kb(total)}kB` + (top.length === 0 ? "" : ` meshQueuedTop=${top}`);
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/kernel/outbound-queue-watch.ts
|
|
573
|
+
/**
|
|
574
|
+
* outbound-queue-watch — an outbound socket queue that HOLDS is named, and its
|
|
575
|
+
* release is timestamped (D446).
|
|
576
|
+
*
|
|
577
|
+
* ## The blind spot this closes
|
|
578
|
+
*
|
|
579
|
+
* On 2026-09-10 hub-main's `arrayBuffers` climbed twice at a constant rate
|
|
580
|
+
* (20 MB/min to 1 264 MB; 63 MB/min for 77 min to 4 968 MB), LIVE across a
|
|
581
|
+
* forced compaction, with ~4 % of JS heap alongside, and each time released in
|
|
582
|
+
* one instant with no line from any owner. The investigation excluded "every
|
|
583
|
+
* socket write queue" by summing `writableLength` over
|
|
584
|
+
* `process._getActiveHandles()` — 84 MB, all of it the phone's WebSocket.
|
|
585
|
+
*
|
|
586
|
+
* `_getActiveHandles()` lists only handles that keep the loop alive. An
|
|
587
|
+
* UNREF'D socket is not one of them, and Moleculer's `TcpWriter.connect`
|
|
588
|
+
* unrefs every socket it opens to a peer node (`transporters/tcp/tcp-writer.js`,
|
|
589
|
+
* `socket.unref()`). So the one outbound plane that writes to another MACHINE
|
|
590
|
+
* — where a reader that stops draining is the ordinary failure — was never in
|
|
591
|
+
* the sum. The UDS children were in it, but only as a total, at one instant,
|
|
592
|
+
* by hand.
|
|
593
|
+
*
|
|
594
|
+
* ## What this is
|
|
595
|
+
*
|
|
596
|
+
* A pure gate over per-peer queue depths the heartbeat reads on every probe:
|
|
597
|
+
* the UDS children's `socket.writableLength` (`SocketChannel.queuedBytes`) and
|
|
598
|
+
* the mesh writer sockets' (`mesh-queue-report.ts`). It says three things and
|
|
599
|
+
* nothing else:
|
|
600
|
+
*
|
|
601
|
+
* - a peer whose queue has been at or above {@link OUTBOUND_QUEUE_THRESHOLD_BYTES}
|
|
602
|
+
* for {@link OUTBOUND_QUEUE_SUSTAIN_MS} is NAMED, with its current and peak
|
|
603
|
+
* depth and how long it has held — at most once per
|
|
604
|
+
* {@link OUTBOUND_QUEUE_COOLDOWN_MS};
|
|
605
|
+
* - a named peer whose queue then falls under half the threshold, or that
|
|
606
|
+
* vanishes from the readings (its socket died — which is how a queue is
|
|
607
|
+
* dropped in one instant), is reported as RELEASED with its peak and the
|
|
608
|
+
* hold, so the release the episodes never logged has a line and a peer;
|
|
609
|
+
* - a burst that drains inside the sustain window says nothing. Backpressure
|
|
610
|
+
* measures LIVENESS, not identity (D444): a peer momentarily behind is
|
|
611
|
+
* coming back.
|
|
612
|
+
*
|
|
613
|
+
* ## What it is NOT
|
|
614
|
+
*
|
|
615
|
+
* Not a bound. It terminates nothing: a UDS child whose channel is destroyed
|
|
616
|
+
* loses every in-flight call, and a mesh socket Moleculer reconnects on its
|
|
617
|
+
* own terms. The WS guard (`ws-slow-consumer-guard.ts`) bounds the one plane
|
|
618
|
+
* whose client is expendable. Here the line is the deliverable; what to do
|
|
619
|
+
* about a peer that holds is decided with the peer named, not before.
|
|
620
|
+
*/
|
|
621
|
+
/** 64 MiB. Below this a queue is a burst on a busy peer (the orchestrator's
|
|
622
|
+
* channel moves ~1.5 MB/s of audio responses); above it, and held, it is
|
|
623
|
+
* retention with a name. */
|
|
624
|
+
var OUTBOUND_QUEUE_THRESHOLD_BYTES = 64 * 1024 * 1024;
|
|
625
|
+
/** Matches the ArrayBuffer census sustain: a minute above the bound. */
|
|
626
|
+
var OUTBOUND_QUEUE_SUSTAIN_MS = 6e4;
|
|
627
|
+
/** A held queue is re-named every ten minutes, not every probe. */
|
|
628
|
+
var OUTBOUND_QUEUE_COOLDOWN_MS = 10 * 6e4;
|
|
629
|
+
/** A release is a fall under this fraction of the threshold. */
|
|
630
|
+
var RELEASE_RATIO = .5;
|
|
631
|
+
/**
|
|
632
|
+
* The pure decision — threshold, sustain, cooldown, release, gone — testable
|
|
633
|
+
* without a socket.
|
|
634
|
+
*/
|
|
635
|
+
function createOutboundQueueGate(options = {}) {
|
|
636
|
+
const thresholdBytes = options.thresholdBytes ?? 67108864;
|
|
637
|
+
const sustainMs = options.sustainMs ?? 6e4;
|
|
638
|
+
const cooldownMs = options.cooldownMs ?? 6e5;
|
|
639
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
640
|
+
const judgeOne = (peerId, bytes, at) => {
|
|
641
|
+
const state = tracked.get(peerId);
|
|
642
|
+
if (bytes >= thresholdBytes) {
|
|
643
|
+
const next = state === void 0 ? {
|
|
644
|
+
aboveSince: at,
|
|
645
|
+
peakBytes: bytes,
|
|
646
|
+
lastNamedAt: null
|
|
647
|
+
} : {
|
|
648
|
+
...state,
|
|
649
|
+
peakBytes: Math.max(state.peakBytes, bytes)
|
|
650
|
+
};
|
|
651
|
+
const heldForMs = at - next.aboveSince;
|
|
652
|
+
const due = heldForMs >= sustainMs && (next.lastNamedAt === null || at - next.lastNamedAt >= cooldownMs);
|
|
653
|
+
tracked.set(peerId, due ? {
|
|
654
|
+
...next,
|
|
655
|
+
lastNamedAt: at
|
|
656
|
+
} : next);
|
|
657
|
+
return due ? {
|
|
658
|
+
kind: "sustained",
|
|
659
|
+
peerId,
|
|
660
|
+
bytes,
|
|
661
|
+
peakBytes: next.peakBytes,
|
|
662
|
+
heldForMs
|
|
663
|
+
} : void 0;
|
|
664
|
+
}
|
|
665
|
+
if (state === void 0) return void 0;
|
|
666
|
+
if (bytes >= thresholdBytes * RELEASE_RATIO) return void 0;
|
|
667
|
+
tracked.delete(peerId);
|
|
668
|
+
if (state.lastNamedAt === null) return void 0;
|
|
669
|
+
return {
|
|
670
|
+
kind: "released",
|
|
671
|
+
peerId,
|
|
672
|
+
peakBytes: state.peakBytes,
|
|
673
|
+
toBytes: bytes,
|
|
674
|
+
heldForMs: at - state.aboveSince,
|
|
675
|
+
gone: false
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
return { observe(readings, at) {
|
|
679
|
+
const verdicts = [];
|
|
680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
681
|
+
for (const reading of readings) {
|
|
682
|
+
seen.add(reading.peerId);
|
|
683
|
+
const verdict = judgeOne(reading.peerId, reading.bytes, at);
|
|
684
|
+
if (verdict !== void 0) verdicts.push(verdict);
|
|
685
|
+
}
|
|
686
|
+
for (const [peerId, state] of tracked) {
|
|
687
|
+
if (seen.has(peerId)) continue;
|
|
688
|
+
tracked.delete(peerId);
|
|
689
|
+
if (state.lastNamedAt === null) continue;
|
|
690
|
+
verdicts.push({
|
|
691
|
+
kind: "released",
|
|
692
|
+
peerId,
|
|
693
|
+
peakBytes: state.peakBytes,
|
|
694
|
+
toBytes: 0,
|
|
695
|
+
heldForMs: at - state.aboveSince,
|
|
696
|
+
gone: true
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
return verdicts;
|
|
700
|
+
} };
|
|
701
|
+
}
|
|
702
|
+
var BYTES_PER_MB$1 = 1048576;
|
|
703
|
+
function mb$2(bytes) {
|
|
704
|
+
return Math.round(bytes / BYTES_PER_MB$1);
|
|
705
|
+
}
|
|
706
|
+
/** `60s` under two minutes, `77m` from there — the hold is read at a glance. */
|
|
707
|
+
function duration(ms) {
|
|
708
|
+
const seconds = Math.round(ms / 1e3);
|
|
709
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)}m`;
|
|
710
|
+
}
|
|
711
|
+
/** The line for one verdict. Both carry the peer, the bytes and the hold;
|
|
712
|
+
* the sustained one also says why the older census could not see it. */
|
|
713
|
+
function formatOutboundQueueVerdict(label, verdict) {
|
|
714
|
+
if (verdict.kind === "sustained") return `[mem] ${label} outbound queue to ${verdict.peerId} has held ${mb$2(verdict.bytes)}MB (peak ${mb$2(verdict.peakBytes)}MB) for ${duration(verdict.heldForMs)} — the peer is not draining what this process writes to it. This is socket.writableLength on the channel itself; a sum over _getActiveHandles() does not list an unref'd socket and would miss it.`;
|
|
715
|
+
return `[mem] ${label} outbound queue to ${verdict.peerId} RELEASED — ${mb$2(verdict.peakBytes)}MB at peak, ${mb$2(verdict.toBytes)}MB now, held ${duration(verdict.heldForMs)}` + (verdict.gone ? " (peer gone: the socket closed and dropped its queue)" : " (drained)");
|
|
429
716
|
}
|
|
430
717
|
//#endregion
|
|
431
718
|
//#region src/kernel/heap-spaces.ts
|
|
@@ -1413,6 +1700,27 @@ function format(label, s, loop, budgetMb, suffix) {
|
|
|
1413
1700
|
return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms${suffix}`;
|
|
1414
1701
|
}
|
|
1415
1702
|
/**
|
|
1703
|
+
* Every outbound queue the heartbeat can reach, as the gate wants them: the
|
|
1704
|
+
* UDS children (only those whose channel keeps a reading — an unmeasured
|
|
1705
|
+
* peer is not a zero) and the mesh writer sockets, each prefixed with its
|
|
1706
|
+
* plane.
|
|
1707
|
+
*/
|
|
1708
|
+
function outboundQueueReadings(uds, mesh) {
|
|
1709
|
+
const readings = [];
|
|
1710
|
+
for (const peer of uds?.peers ?? []) {
|
|
1711
|
+
if (peer.queuedBytes === null) continue;
|
|
1712
|
+
readings.push({
|
|
1713
|
+
peerId: `uds:${peer.peerId}`,
|
|
1714
|
+
bytes: peer.queuedBytes
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
for (const node of mesh ?? []) readings.push({
|
|
1718
|
+
peerId: `mesh:${node.nodeId}`,
|
|
1719
|
+
bytes: node.queuedBytes
|
|
1720
|
+
});
|
|
1721
|
+
return readings;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1416
1724
|
* Start the heartbeat. Returns a stop function.
|
|
1417
1725
|
*
|
|
1418
1726
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
@@ -1526,7 +1834,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1526
1834
|
const due = at - lastLoggedAt >= intervalMs;
|
|
1527
1835
|
if (mode === "escalated" || due) {
|
|
1528
1836
|
lastLoggedAt = at;
|
|
1529
|
-
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()));
|
|
1837
|
+
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()) + formatMeshQueue(meshQueue?.()));
|
|
1530
1838
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
1531
1839
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
1532
1840
|
else sink.info(line);
|
|
@@ -1535,8 +1843,17 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1535
1843
|
checkRssBudget(sample, at);
|
|
1536
1844
|
maybeReclaim(sample);
|
|
1537
1845
|
judgeArrayBuffers(sample, at);
|
|
1846
|
+
judgeOutboundQueues(at);
|
|
1538
1847
|
} catch {}
|
|
1539
1848
|
};
|
|
1849
|
+
const meshQueue = probes?.meshQueue;
|
|
1850
|
+
const queueReader = probes?.socketPlane;
|
|
1851
|
+
const queueGate = queueReader === void 0 && meshQueue === void 0 ? void 0 : createOutboundQueueGate(probes?.outboundQueue);
|
|
1852
|
+
const judgeOutboundQueues = (at) => {
|
|
1853
|
+
if (queueGate === void 0) return;
|
|
1854
|
+
const readings = outboundQueueReadings(queueReader?.(), meshQueue?.());
|
|
1855
|
+
for (const verdict of queueGate.observe(readings, at)) sink.warn(formatOutboundQueueVerdict(label, verdict));
|
|
1856
|
+
};
|
|
1540
1857
|
const censusProbe = probes?.arrayBufferCensus;
|
|
1541
1858
|
const censusGate = censusProbe === void 0 ? void 0 : createArrayBufferCensusGate(censusProbe);
|
|
1542
1859
|
let censusInFlight = false;
|
|
@@ -5333,6 +5650,74 @@ function encodeFrame(value) {
|
|
|
5333
5650
|
Buffer.from(body.buffer, body.byteOffset, body.byteLength).copy(out, 4);
|
|
5334
5651
|
return out;
|
|
5335
5652
|
}
|
|
5653
|
+
/** msgpack `fixstr` of {@link PAYLOAD_KEY}: `0xa0 | length`, then the bytes. */
|
|
5654
|
+
var PAYLOAD_KEY_BYTES = Buffer.from([162, ...Buffer.from("pl")]);
|
|
5655
|
+
/** msgpack `bin 32` marker: one byte, then a 4-byte big-endian length. */
|
|
5656
|
+
var BIN32_MARKER = 198;
|
|
5657
|
+
/** msgpack `fixmap` range: `0x80 | count`, count in `0..15`. */
|
|
5658
|
+
var FIXMAP_BASE = 128;
|
|
5659
|
+
var FIXMAP_MAX_COUNT = 15;
|
|
5660
|
+
/**
|
|
5661
|
+
* Encode the HEAD of a frame that carries an opaque payload: the length
|
|
5662
|
+
* prefix, the msgpack envelope with one extra key ({@link PAYLOAD_KEY}), and
|
|
5663
|
+
* the `bin 32` header for the payload — everything but the payload bytes
|
|
5664
|
+
* themselves, which the caller writes straight after it.
|
|
5665
|
+
*
|
|
5666
|
+
* ## Why the payload is the frame's TAIL and not a field
|
|
5667
|
+
*
|
|
5668
|
+
* hub-main routes every sibling↔sibling call. Until this existed it decoded
|
|
5669
|
+
* the whole request (args included), built a new message, and re-encoded it
|
|
5670
|
+
* — walking a 13.5 KB PCM chunk or a 2 000-row settings answer twice on the
|
|
5671
|
+
* one thread that serves every tRPC request, and allocating an `Encoder`
|
|
5672
|
+
* buffer that doubles from 2 KiB up to the body's size on every write
|
|
5673
|
+
* (D441: ~27 % of hub-main's busy time in msgpack decode, ~6 % in encode,
|
|
5674
|
+
* and the churn that fed a 29.5 % GC share).
|
|
5675
|
+
*
|
|
5676
|
+
* With the payload appended as the LAST key of the map, the receiver's
|
|
5677
|
+
* ordinary msgpack decode yields it as a zero-copy VIEW of the frame
|
|
5678
|
+
* (`@msgpack/msgpack` decodes `bin` as `subarray`), the router reads only the
|
|
5679
|
+
* envelope, and forwarding is: encode a ~60-byte head, write it, write the
|
|
5680
|
+
* view. The wire stays plain msgpack — a peer that never heard of payloads
|
|
5681
|
+
* decodes `{ …frame, pl: <bytes> }` and can be told, at register, not to be
|
|
5682
|
+
* sent one.
|
|
5683
|
+
*
|
|
5684
|
+
* ## The one hand-written msgpack in this repository
|
|
5685
|
+
*
|
|
5686
|
+
* `@msgpack/msgpack` has no "encode a map, then append a key" API, so the
|
|
5687
|
+
* envelope is encoded normally and its `fixmap` count byte is bumped by one
|
|
5688
|
+
* before the key and the `bin 32` header are appended. That patch is only
|
|
5689
|
+
* valid for a `fixmap` (≤ 15 keys); a `Frame` has at most four, and anything
|
|
5690
|
+
* else is refused rather than silently corrupted.
|
|
5691
|
+
*/
|
|
5692
|
+
function encodeFrameHead(frame, payload) {
|
|
5693
|
+
const envelope = encode(frame, ENCODE_OPTIONS);
|
|
5694
|
+
const first = envelope[0];
|
|
5695
|
+
if (envelope.byteLength === 0 || first === void 0 || first < FIXMAP_BASE || first > 142) throw new Error(`encodeFrameHead: envelope must be a msgpack fixmap with at most ${FIXMAP_MAX_COUNT - 1} keys (got leading byte 0x${(first ?? 0).toString(16)})`);
|
|
5696
|
+
const bodyLength = envelope.byteLength + PAYLOAD_KEY_BYTES.byteLength + 5 + payload.byteLength;
|
|
5697
|
+
const head = Buffer.allocUnsafe(HEADER_BYTES + envelope.byteLength + PAYLOAD_KEY_BYTES.byteLength + 5);
|
|
5698
|
+
head.writeUInt32BE(bodyLength, 0);
|
|
5699
|
+
Buffer.from(envelope.buffer, envelope.byteOffset, envelope.byteLength).copy(head, HEADER_BYTES);
|
|
5700
|
+
head[HEADER_BYTES] = first + 1;
|
|
5701
|
+
let at = HEADER_BYTES + envelope.byteLength;
|
|
5702
|
+
PAYLOAD_KEY_BYTES.copy(head, at);
|
|
5703
|
+
at += PAYLOAD_KEY_BYTES.byteLength;
|
|
5704
|
+
head[at] = BIN32_MARKER;
|
|
5705
|
+
head.writeUInt32BE(payload.byteLength, at + 1);
|
|
5706
|
+
return head;
|
|
5707
|
+
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Serialise a value into the bytes a payload carries. The SAME options as the
|
|
5710
|
+
* envelope codec, so `undefined` properties are omitted rather than turned
|
|
5711
|
+
* into `null` — a `z.void()` cap output must read back as `undefined` on the
|
|
5712
|
+
* far side exactly as it did when it travelled inline.
|
|
5713
|
+
*/
|
|
5714
|
+
function packPayload(value) {
|
|
5715
|
+
return encode(value, ENCODE_OPTIONS);
|
|
5716
|
+
}
|
|
5717
|
+
/** Inverse of {@link packPayload}. A `bin` inside decodes as a view of `bytes`. */
|
|
5718
|
+
function unpackPayload(bytes) {
|
|
5719
|
+
return decode(bytes);
|
|
5720
|
+
}
|
|
5336
5721
|
/** Bytes in the big-endian length prefix that opens every frame. */
|
|
5337
5722
|
var HEADER_BYTES = 4;
|
|
5338
5723
|
/**
|
|
@@ -5491,6 +5876,25 @@ function localEndpointPath(nodeId) {
|
|
|
5491
5876
|
if (process.platform === "win32") return `\\\\.\\pipe\\camstack-${safe}`;
|
|
5492
5877
|
return join(tmpdir(), `camstack-${safe}.sock`);
|
|
5493
5878
|
}
|
|
5879
|
+
//#endregion
|
|
5880
|
+
//#region src/kernel/transport/local-transport.ts
|
|
5881
|
+
/**
|
|
5882
|
+
* An answer that carries an opaque payload beside its body.
|
|
5883
|
+
*
|
|
5884
|
+
* A CLASS rather than a shape so the channel can tell it from a provider's
|
|
5885
|
+
* ordinary `{ body, payload }` return value by identity, never by duck-typing:
|
|
5886
|
+
* a cap method is free to return an object with those two keys and it must
|
|
5887
|
+
* travel inline like any other. `request()` resolves with one of these only
|
|
5888
|
+
* when the peer's `res` frame carried a payload.
|
|
5889
|
+
*/
|
|
5890
|
+
var PayloadReply = class {
|
|
5891
|
+
body;
|
|
5892
|
+
payload;
|
|
5893
|
+
constructor(body, payload) {
|
|
5894
|
+
this.body = body;
|
|
5895
|
+
this.payload = payload;
|
|
5896
|
+
}
|
|
5897
|
+
};
|
|
5494
5898
|
var SocketChannel = class {
|
|
5495
5899
|
socket;
|
|
5496
5900
|
nextId = 1;
|
|
@@ -5547,7 +5951,7 @@ var SocketChannel = class {
|
|
|
5547
5951
|
k: "req",
|
|
5548
5952
|
id,
|
|
5549
5953
|
body
|
|
5550
|
-
});
|
|
5954
|
+
}, opts?.payload);
|
|
5551
5955
|
});
|
|
5552
5956
|
}
|
|
5553
5957
|
emit(body) {
|
|
@@ -5587,11 +5991,38 @@ var SocketChannel = class {
|
|
|
5587
5991
|
rx: sampleSocketDirection(this.rxCounters)
|
|
5588
5992
|
};
|
|
5589
5993
|
}
|
|
5590
|
-
|
|
5994
|
+
/**
|
|
5995
|
+
* Bytes handed to the socket that the kernel has not taken yet — the
|
|
5996
|
+
* `Writable` buffer plus the write in flight. Read LIVE, never cached: this
|
|
5997
|
+
* is the number that says whether the peer is draining what we write, and
|
|
5998
|
+
* it is read on every heartbeat probe (D446). An unref'd socket keeps it
|
|
5999
|
+
* exactly like a ref'd one, which is why it is read here and not summed
|
|
6000
|
+
* over `_getActiveHandles()`.
|
|
6001
|
+
*/
|
|
6002
|
+
queuedBytes() {
|
|
6003
|
+
return this.socket.writableLength;
|
|
6004
|
+
}
|
|
6005
|
+
/**
|
|
6006
|
+
* Write one frame. With a payload the head and the tail are two `write`
|
|
6007
|
+
* calls under `cork()` — the kernel sees one `writev`, and the payload bytes
|
|
6008
|
+
* are never copied into the head. A forwarded payload is a view into the
|
|
6009
|
+
* frame it arrived on, and that frame's buffer is never mutated, so handing
|
|
6010
|
+
* the view to the socket is safe.
|
|
6011
|
+
*/
|
|
6012
|
+
send(frame, payload) {
|
|
5591
6013
|
if (this.closed) return;
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
6014
|
+
if (payload === void 0) {
|
|
6015
|
+
const encoded = encodeFrame(frame);
|
|
6016
|
+
recordSocketFrame(this.txCounters, frame.k, encoded.byteLength);
|
|
6017
|
+
this.socket.write(encoded);
|
|
6018
|
+
return;
|
|
6019
|
+
}
|
|
6020
|
+
const head = encodeFrameHead(frame, payload);
|
|
6021
|
+
recordSocketFrame(this.txCounters, frame.k, head.byteLength + payload.byteLength);
|
|
6022
|
+
this.socket.cork();
|
|
6023
|
+
this.socket.write(head);
|
|
6024
|
+
this.socket.write(payload);
|
|
6025
|
+
this.socket.uncork();
|
|
5595
6026
|
}
|
|
5596
6027
|
onData(chunk) {
|
|
5597
6028
|
const frames = this.decoder.push(chunk);
|
|
@@ -5611,8 +6042,14 @@ var SocketChannel = class {
|
|
|
5611
6042
|
recordSocketFrame(this.rxCounters, frame.k, wireBytes);
|
|
5612
6043
|
if (frame.k === "req") {
|
|
5613
6044
|
try {
|
|
5614
|
-
const result = await this.requestHandler(frame.body);
|
|
5615
|
-
this.send({
|
|
6045
|
+
const result = await this.requestHandler(frame.body, frame.pl);
|
|
6046
|
+
if (result instanceof PayloadReply) this.send({
|
|
6047
|
+
k: "res",
|
|
6048
|
+
id: frame.id,
|
|
6049
|
+
ok: true,
|
|
6050
|
+
body: result.body
|
|
6051
|
+
}, result.payload);
|
|
6052
|
+
else this.send({
|
|
5616
6053
|
k: "res",
|
|
5617
6054
|
id: frame.id,
|
|
5618
6055
|
ok: true,
|
|
@@ -5633,8 +6070,9 @@ var SocketChannel = class {
|
|
|
5633
6070
|
if (!p) return;
|
|
5634
6071
|
this.pending.delete(frame.id);
|
|
5635
6072
|
if (p.timer !== null) clearTimeout(p.timer);
|
|
5636
|
-
if (frame.ok) p.
|
|
5637
|
-
else p.
|
|
6073
|
+
if (!frame.ok) p.reject(new Error(frame.err));
|
|
6074
|
+
else if (frame.pl !== void 0) p.resolve(new PayloadReply(frame.body, frame.pl));
|
|
6075
|
+
else p.resolve(frame.body);
|
|
5638
6076
|
return;
|
|
5639
6077
|
}
|
|
5640
6078
|
this.eventHandler(frame.body);
|
|
@@ -6185,572 +6623,162 @@ function deliverShared(state, event) {
|
|
|
6185
6623
|
}
|
|
6186
6624
|
}
|
|
6187
6625
|
//#endregion
|
|
6188
|
-
//#region src/kernel/
|
|
6189
|
-
/**
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6626
|
+
//#region src/kernel/transport/cap-routing-hints.ts
|
|
6627
|
+
/** Lift {@link CapRoutingHints} from a call's args. Never throws. */
|
|
6628
|
+
function liftRoutingHints(args) {
|
|
6629
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return {};
|
|
6630
|
+
const nodeId = Reflect.get(args, "nodeId");
|
|
6631
|
+
const addonId = Reflect.get(args, "addonId");
|
|
6632
|
+
return {
|
|
6633
|
+
...typeof nodeId === "string" && nodeId.length > 0 ? { nodeId } : {},
|
|
6634
|
+
...typeof addonId === "string" && addonId.length > 0 ? { addonId } : {}
|
|
6635
|
+
};
|
|
6636
|
+
}
|
|
6637
|
+
//#endregion
|
|
6638
|
+
//#region src/kernel/transport/local-child-registry.ts
|
|
6639
|
+
function readFanoutMode() {
|
|
6640
|
+
const raw = process.env.CAMSTACK_UDS_EVENT_FANOUT;
|
|
6641
|
+
if (raw === "shadow" || raw === "broadcast") return raw;
|
|
6642
|
+
return "filter";
|
|
6197
6643
|
}
|
|
6198
6644
|
/**
|
|
6199
|
-
*
|
|
6200
|
-
*
|
|
6645
|
+
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
6646
|
+
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
6647
|
+
* prefix to distinguish routing failures (safe to retry via broker) from real
|
|
6648
|
+
* provider errors (must NOT retry to avoid double-executing side effects).
|
|
6201
6649
|
*/
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6650
|
+
var UDS_NO_ROUTE_PREFIX = "UDS_NO_ROUTE";
|
|
6651
|
+
/**
|
|
6652
|
+
* Parent-side authority for local addon-runners reachable over a
|
|
6653
|
+
* `LocalTransportServer` (UDS). Children register their cap manifest on
|
|
6654
|
+
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
6655
|
+
* owning child over the channel and drops a child's caps on disconnect.
|
|
6656
|
+
*/
|
|
6657
|
+
var LocalChildRegistry = class {
|
|
6658
|
+
children = /* @__PURE__ */ new Map();
|
|
6659
|
+
registeredHandler = () => {};
|
|
6660
|
+
goneHandler = () => {};
|
|
6661
|
+
/** Source of {@link RegisteredChild.incarnation}; one bump per accepted connection. */
|
|
6662
|
+
nextIncarnation = 1;
|
|
6663
|
+
eventHandler = null;
|
|
6664
|
+
logHandler = null;
|
|
6665
|
+
readinessHandler = null;
|
|
6666
|
+
server;
|
|
6667
|
+
onUnownedCall;
|
|
6668
|
+
logger;
|
|
6669
|
+
getActiveSingletonAddonId;
|
|
6670
|
+
resolveChildIdForAddon;
|
|
6671
|
+
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
6672
|
+
ownNodeId;
|
|
6673
|
+
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
6674
|
+
isAggregatedCollectionMethod;
|
|
6675
|
+
isAddonPinnedCall;
|
|
6676
|
+
/** See {@link ForwardCensus}. Mutable counters on the hot path, by design. */
|
|
6677
|
+
forwardedRaw = 0;
|
|
6678
|
+
forwardedDecoded = 0;
|
|
6679
|
+
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
6680
|
+
capTimeoutMs;
|
|
6681
|
+
/** See {@link LocalChildRegistryOptions.capUsageObserver}. */
|
|
6682
|
+
capUsageObserver;
|
|
6683
|
+
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
6684
|
+
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
6685
|
+
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
6686
|
+
fanoutMode = readFanoutMode();
|
|
6687
|
+
/** Per-child event fan-out counters (sent / suppressed). */
|
|
6688
|
+
childEventStats = /* @__PURE__ */ new Map();
|
|
6689
|
+
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
6690
|
+
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
6691
|
+
/**
|
|
6692
|
+
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
6693
|
+
* or a full `LocalChildRegistryOptions` object.
|
|
6694
|
+
*
|
|
6695
|
+
* Positional overloads (existing call sites are unchanged):
|
|
6696
|
+
* new LocalChildRegistry(server)
|
|
6697
|
+
* new LocalChildRegistry(server, onUnownedCall)
|
|
6698
|
+
*
|
|
6699
|
+
* Options object (new call sites that pass a logger):
|
|
6700
|
+
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
6701
|
+
*/
|
|
6702
|
+
constructor(serverOrOptions, onUnownedCallArg) {
|
|
6703
|
+
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
6704
|
+
const opts = serverOrOptions;
|
|
6705
|
+
this.server = opts.server;
|
|
6706
|
+
this.onUnownedCall = opts.onUnownedCall;
|
|
6707
|
+
this.logger = opts.logger;
|
|
6708
|
+
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
6709
|
+
this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
|
|
6710
|
+
this.ownNodeId = opts.ownNodeId;
|
|
6711
|
+
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
6712
|
+
this.isAddonPinnedCall = opts.isAddonPinnedCall;
|
|
6713
|
+
this.capTimeoutMs = opts.capTimeoutMs;
|
|
6714
|
+
this.capUsageObserver = opts.capUsageObserver;
|
|
6715
|
+
} else {
|
|
6716
|
+
this.server = serverOrOptions;
|
|
6717
|
+
this.onUnownedCall = onUnownedCallArg;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
async start() {
|
|
6721
|
+
this.logger?.info("UDS event fan-out mode", { mode: this.fanoutMode });
|
|
6722
|
+
this.server.onConnection((channel) => this.onConnection(channel));
|
|
6723
|
+
await this.server.listen();
|
|
6209
6724
|
}
|
|
6210
|
-
}
|
|
6211
|
-
//#endregion
|
|
6212
|
-
//#region src/kernel/transport/cap-route.ts
|
|
6213
|
-
function buildMessage(capName, method, detail) {
|
|
6214
|
-
const call = method !== void 0 ? `${capName}.${method}` : capName;
|
|
6215
|
-
const target = detail.nodeId !== void 0 ? ` to node ${detail.nodeId}` : "";
|
|
6216
|
-
const rejectedStr = detail.rejected.map((r) => `${r.kind}=${r.why}`).join("; ");
|
|
6217
|
-
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
6218
|
-
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
6219
|
-
}
|
|
6220
|
-
var CapRouteError = class extends Error {
|
|
6221
|
-
reason;
|
|
6222
|
-
nodeId;
|
|
6223
|
-
rejected;
|
|
6224
6725
|
/**
|
|
6225
|
-
*
|
|
6226
|
-
*
|
|
6227
|
-
*
|
|
6726
|
+
* Per-child event fan-out counters (`{ sent, suppressed }`) or `null` if the
|
|
6727
|
+
* child has no recorded events yet. Exposed for the starvation-verification
|
|
6728
|
+
* surface (shadow burn-in + operator debug).
|
|
6228
6729
|
*/
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6730
|
+
getChildEventStats(childId) {
|
|
6731
|
+
const s = this.childEventStats.get(childId);
|
|
6732
|
+
return s === void 0 ? null : {
|
|
6733
|
+
sent: s.sent,
|
|
6734
|
+
suppressed: s.suppressed
|
|
6735
|
+
};
|
|
6235
6736
|
}
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
*
|
|
6239
|
-
*
|
|
6240
|
-
*
|
|
6241
|
-
*
|
|
6242
|
-
*
|
|
6243
|
-
*
|
|
6244
|
-
*
|
|
6245
|
-
*
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
6252
|
-
*
|
|
6253
|
-
* PURE: no side effects, no async, no broker/registry imports.
|
|
6254
|
-
*/
|
|
6255
|
-
function classifyCapRoute(capName, opts, snapshot) {
|
|
6256
|
-
const rejected = [];
|
|
6257
|
-
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
6258
|
-
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
6259
|
-
}
|
|
6260
|
-
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
6261
|
-
if (nodeId === snap.hubNodeId) {
|
|
6262
|
-
const residentProvides = snap.hubResidentProvides;
|
|
6263
|
-
const providesOnHub = residentProvides ?? snap.hubInProcessProvides;
|
|
6264
|
-
const getRef = residentProvides !== void 0 ? snap.getHubResidentProviderRef : snap.getInProcessProviderRef;
|
|
6265
|
-
if (providesOnHub(capName)) {
|
|
6266
|
-
const ref = getRef?.(capName) ?? null;
|
|
6267
|
-
if (ref !== null) return {
|
|
6268
|
-
kind: "hub-in-process",
|
|
6269
|
-
capName,
|
|
6270
|
-
ref
|
|
6271
|
-
};
|
|
6272
|
-
rejected.push({
|
|
6273
|
-
kind: "hub-in-process",
|
|
6274
|
-
why: "provider ref not available in snapshot"
|
|
6275
|
-
});
|
|
6276
|
-
throw new CapRouteError(capName, void 0, {
|
|
6277
|
-
reason: "no-provider",
|
|
6278
|
-
nodeId,
|
|
6279
|
-
rejected
|
|
6280
|
-
});
|
|
6281
|
-
}
|
|
6282
|
-
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
6283
|
-
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6284
|
-
if (childId !== null) return {
|
|
6285
|
-
kind: "hub-local-uds",
|
|
6286
|
-
capName,
|
|
6287
|
-
childId
|
|
6288
|
-
};
|
|
6289
|
-
rejected.push({
|
|
6290
|
-
kind: "hub-local-uds",
|
|
6291
|
-
why: "child id not resolvable from snapshot"
|
|
6292
|
-
});
|
|
6293
|
-
throw new CapRouteError(capName, void 0, {
|
|
6294
|
-
reason: "no-provider",
|
|
6295
|
-
nodeId,
|
|
6296
|
-
rejected
|
|
6297
|
-
});
|
|
6298
|
-
}
|
|
6299
|
-
rejected.push({
|
|
6300
|
-
kind: "hub-in-process",
|
|
6301
|
-
why: "hub does not provide this cap in-process"
|
|
6302
|
-
});
|
|
6303
|
-
rejected.push({
|
|
6304
|
-
kind: "hub-local-uds",
|
|
6305
|
-
why: "no hub-local child provides this cap"
|
|
6306
|
-
});
|
|
6307
|
-
throw new CapRouteError(capName, void 0, {
|
|
6308
|
-
reason: "no-provider",
|
|
6309
|
-
nodeId,
|
|
6310
|
-
rejected
|
|
6311
|
-
});
|
|
6312
|
-
}
|
|
6313
|
-
if (!snap.nodeOnline(nodeId)) {
|
|
6314
|
-
rejected.push({
|
|
6315
|
-
kind: "remote-moleculer",
|
|
6316
|
-
why: `node ${nodeId} is offline`
|
|
6317
|
-
});
|
|
6318
|
-
throw new CapRouteError(capName, void 0, {
|
|
6319
|
-
reason: "node-offline",
|
|
6320
|
-
nodeId,
|
|
6321
|
-
rejected
|
|
6322
|
-
});
|
|
6737
|
+
/**
|
|
6738
|
+
* This child's socket traffic — bytes and messages, both directions, split by
|
|
6739
|
+
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
6740
|
+
* counters (an in-process or test transport).
|
|
6741
|
+
*
|
|
6742
|
+
* The event counters above cover ONE kind of frame and, once surfaced on
|
|
6743
|
+
* 2026-08-29, accounted for 4% of hub-main's ~44 000 socket syscalls/s. This
|
|
6744
|
+
* is the same question asked of every frame the channel moves, and it is the
|
|
6745
|
+
* only per-peer attribution that exists: `/proc` reports one `rchar`/`wchar`
|
|
6746
|
+
* pair for the whole process and has no per-socket breakdown.
|
|
6747
|
+
*/
|
|
6748
|
+
getChildSocketTraffic(childId) {
|
|
6749
|
+
const entry = this.children.get(childId);
|
|
6750
|
+
if (entry === void 0) return null;
|
|
6751
|
+
return entry.channel.readTraffic?.() ?? null;
|
|
6323
6752
|
}
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
});
|
|
6335
|
-
throw new CapRouteError(capName, void 0, {
|
|
6336
|
-
reason: "no-provider",
|
|
6337
|
-
nodeId,
|
|
6338
|
-
rejected
|
|
6339
|
-
});
|
|
6753
|
+
/**
|
|
6754
|
+
* Bytes this process has written to a child that the child has not yet
|
|
6755
|
+
* taken, live, or `null` when the channel keeps no such reading. Read on
|
|
6756
|
+
* every heartbeat probe (D446) — a child that stops draining is the shape
|
|
6757
|
+
* of an outbound-queue episode, and the number lives on the channel.
|
|
6758
|
+
*/
|
|
6759
|
+
getChildQueuedBytes(childId) {
|
|
6760
|
+
const entry = this.children.get(childId);
|
|
6761
|
+
if (entry === void 0) return null;
|
|
6762
|
+
return entry.channel.queuedBytes?.() ?? null;
|
|
6340
6763
|
}
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
}
|
|
6347
|
-
function classifySingleton(capName, deviceId, snap, rejected) {
|
|
6348
|
-
if (snap.hubInProcessProvides(capName)) {
|
|
6349
|
-
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
6350
|
-
if (ref !== null) return {
|
|
6351
|
-
kind: "hub-in-process",
|
|
6352
|
-
capName,
|
|
6353
|
-
ref
|
|
6354
|
-
};
|
|
6355
|
-
rejected.push({
|
|
6356
|
-
kind: "hub-in-process",
|
|
6357
|
-
why: "provider ref not available in snapshot"
|
|
6358
|
-
});
|
|
6359
|
-
} else rejected.push({
|
|
6360
|
-
kind: "hub-in-process",
|
|
6361
|
-
why: "hub does not provide this cap in-process"
|
|
6362
|
-
});
|
|
6363
|
-
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
6364
|
-
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6365
|
-
if (childId !== null) return {
|
|
6366
|
-
kind: "hub-local-uds",
|
|
6367
|
-
capName,
|
|
6368
|
-
childId
|
|
6764
|
+
/** Cumulative {@link ForwardCensus} of this parent's `cap-call-out` routing. */
|
|
6765
|
+
readForwardCensus() {
|
|
6766
|
+
return {
|
|
6767
|
+
raw: this.forwardedRaw,
|
|
6768
|
+
decoded: this.forwardedDecoded
|
|
6369
6769
|
};
|
|
6370
|
-
rejected.push({
|
|
6371
|
-
kind: "hub-local-uds",
|
|
6372
|
-
why: "child id not resolvable from snapshot"
|
|
6373
|
-
});
|
|
6374
|
-
} else rejected.push({
|
|
6375
|
-
kind: "hub-local-uds",
|
|
6376
|
-
why: "no hub-local child provides this cap"
|
|
6377
|
-
});
|
|
6378
|
-
const knownNodes = snap.listKnownNodeIds?.() ?? [];
|
|
6379
|
-
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
6380
|
-
kind: "remote-moleculer",
|
|
6381
|
-
capName,
|
|
6382
|
-
nodeId
|
|
6383
|
-
};
|
|
6384
|
-
rejected.push({
|
|
6385
|
-
kind: "remote-moleculer",
|
|
6386
|
-
why: "no online remote node knows this cap"
|
|
6387
|
-
});
|
|
6388
|
-
throw new CapRouteError(capName, void 0, {
|
|
6389
|
-
reason: "no-provider",
|
|
6390
|
-
rejected
|
|
6391
|
-
});
|
|
6392
|
-
}
|
|
6393
|
-
//#endregion
|
|
6394
|
-
//#region src/kernel/transport/cap-route-resolver.ts
|
|
6395
|
-
/** The Moleculer service name that Task 6's agent registers. */
|
|
6396
|
-
var AGENT_CAP_FWD_SERVICE = "$agent-cap-fwd";
|
|
6397
|
-
/** The Moleculer action (service.action) for agent cap forwarding. */
|
|
6398
|
-
var AGENT_CAP_FWD_ACTION = `${AGENT_CAP_FWD_SERVICE}.forward`;
|
|
6399
|
-
/** Default timeout for remote Moleculer cap calls (ms). */
|
|
6400
|
-
var REMOTE_CALL_TIMEOUT_MS = 6e4;
|
|
6401
|
-
function extractDeviceId(args) {
|
|
6402
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
6403
|
-
const raw = Reflect.get(args, "deviceId");
|
|
6404
|
-
return typeof raw === "number" ? raw : void 0;
|
|
6405
|
-
}
|
|
6406
|
-
/**
|
|
6407
|
-
* Extract an inline `nodeId` string from a cap call's args — the per-call node
|
|
6408
|
-
* pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
|
|
6409
|
-
* device-scoped caps carry `deviceId`, and the way the generated cap-router on
|
|
6410
|
-
* the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
|
|
6411
|
-
*
|
|
6412
|
-
* This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
|
|
6413
|
-
* benchmark addon running a synthetic/decoder workload ON a chosen agent). The
|
|
6414
|
-
* out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
|
|
6415
|
-
* hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
|
|
6416
|
-
* methods that don't declare `nodeId` simply ignore the extra field (they
|
|
6417
|
-
* destructure the fields they need); methods that DO declare it (e.g.
|
|
6418
|
-
* `getEngineProvisioning({nodeId})`) still receive it unchanged.
|
|
6419
|
-
*/
|
|
6420
|
-
function extractNodeId(args) {
|
|
6421
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
6422
|
-
const raw = Reflect.get(args, "nodeId");
|
|
6423
|
-
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
6424
|
-
}
|
|
6425
|
-
/**
|
|
6426
|
-
* Merge `callerAddonId` into `args` as an extra field — the SAME "provider
|
|
6427
|
-
* destructures what it needs and ignores the rest" convention
|
|
6428
|
-
* {@link extractNodeId} already relies on for the inline `nodeId` pin.
|
|
6429
|
-
* `undefined` (the overwhelmingly common case: no caller hint, or the census
|
|
6430
|
-
* consuming it is disarmed) returns `args` unchanged — no allocation. A
|
|
6431
|
-
* non-object `args` (or `null`/array) is returned unchanged too: there is
|
|
6432
|
-
* nowhere to attach an extra key without changing the payload's shape, and a
|
|
6433
|
-
* provider taking a bare value never reads `callerAddonId` off it anyway.
|
|
6434
|
-
*/
|
|
6435
|
-
function withCallerAddonId(args, callerAddonId) {
|
|
6436
|
-
if (callerAddonId === void 0) return args;
|
|
6437
|
-
if (args === null || typeof args !== "object" || Array.isArray(args)) return args;
|
|
6438
|
-
return {
|
|
6439
|
-
...args,
|
|
6440
|
-
callerAddonId
|
|
6441
|
-
};
|
|
6442
|
-
}
|
|
6443
|
-
var CapRouteResolver = class {
|
|
6444
|
-
hubNodeId;
|
|
6445
|
-
broker;
|
|
6446
|
-
hubLocalRegistry;
|
|
6447
|
-
nodeAuthority;
|
|
6448
|
-
inProcessProviders;
|
|
6449
|
-
hubResidentProviders;
|
|
6450
|
-
capTimeoutMs;
|
|
6451
|
-
snapshot;
|
|
6452
|
-
constructor(deps) {
|
|
6453
|
-
this.hubNodeId = deps.hubNodeId;
|
|
6454
|
-
this.broker = deps.broker;
|
|
6455
|
-
this.hubLocalRegistry = deps.hubLocalRegistry;
|
|
6456
|
-
this.nodeAuthority = deps.nodeAuthority;
|
|
6457
|
-
this.inProcessProviders = deps.inProcessProviders;
|
|
6458
|
-
this.hubResidentProviders = deps.hubResidentProviders;
|
|
6459
|
-
this.capTimeoutMs = deps.capTimeoutMs;
|
|
6460
|
-
this.snapshot = this.buildSnapshot();
|
|
6461
|
-
}
|
|
6462
|
-
/** Per-call RPC timeout: the cap method's declared override, else the default. */
|
|
6463
|
-
callTimeout(capName, method) {
|
|
6464
|
-
return this.capTimeoutMs?.(capName, method) ?? REMOTE_CALL_TIMEOUT_MS;
|
|
6465
|
-
}
|
|
6466
|
-
resolveCapRoute(capName, opts) {
|
|
6467
|
-
return classifyCapRoute(capName, opts, this.snapshot);
|
|
6468
6770
|
}
|
|
6469
6771
|
/**
|
|
6470
|
-
*
|
|
6471
|
-
*
|
|
6472
|
-
*
|
|
6473
|
-
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
6474
|
-
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
6475
|
-
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
6476
|
-
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
6477
|
-
* active provider), but WRONG for the native-cap fallback
|
|
6478
|
-
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
6479
|
-
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
6480
|
-
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
6481
|
-
* itself, the native is never invoked, and the wrapper silently falls
|
|
6482
|
-
* through to its secondary strategy.
|
|
6772
|
+
* The regime the counters above were produced under, read once from
|
|
6773
|
+
* `CAMSTACK_UDS_EVENT_FANOUT` at construction.
|
|
6483
6774
|
*
|
|
6484
|
-
*
|
|
6485
|
-
*
|
|
6486
|
-
*
|
|
6487
|
-
*
|
|
6488
|
-
* caller then falls through to its remote-resolution branch.
|
|
6775
|
+
* A suppressed count means "nothing WAS dropped" under `filter` and "nothing
|
|
6776
|
+
* WOULD have been dropped" under `shadow`. Those are different claims, and
|
|
6777
|
+
* until this getter existed the mode was invisible at runtime — the counters
|
|
6778
|
+
* were readable and the regime that produced them was not.
|
|
6489
6779
|
*/
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
const childId = this.snapshot.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6493
|
-
if (childId === null) return null;
|
|
6494
|
-
return {
|
|
6495
|
-
kind: "hub-local-uds",
|
|
6496
|
-
capName,
|
|
6497
|
-
childId
|
|
6498
|
-
};
|
|
6499
|
-
}
|
|
6500
|
-
/**
|
|
6501
|
-
* @param addonId Optional out-of-band PROVIDER-selection hint (Task 7.5a):
|
|
6502
|
-
* the addonId of the provider the CALLER already resolved (e.g.
|
|
6503
|
-
* `buildCapCallFn`'s `deps.addonId` for a registered grouped-runner
|
|
6504
|
-
* provider). Only meaningful for `agent-child-forward` — a `remote-moleculer`
|
|
6505
|
-
* route already encodes the provider in its action name
|
|
6506
|
-
* (`${addonId}.${cap}.${method}`, via `NodeCapAuthority.getAddonId`), and a
|
|
6507
|
-
* `hub-local-uds` caller that already knows the addonId (`buildCapCallFn`)
|
|
6508
|
-
* talks to `LocalChildRegistry.callCapOnChild` directly rather than through
|
|
6509
|
-
* `dispatch`.
|
|
6510
|
-
* @param callerAddonId Optional out-of-band CALLER-identity hint — the
|
|
6511
|
-
* addon that ORIGINATED the call, distinct from `addonId` above (which
|
|
6512
|
-
* names the resolved PROVIDER, not the caller). Set by
|
|
6513
|
-
* `onUnownedCall` from `CapCallInput.callerAddonId`
|
|
6514
|
-
* (`LocalChildRegistry` stamped it off the registered `childId`). Only
|
|
6515
|
-
* the `hub-in-process` branch consumes it — merged into `args` so a
|
|
6516
|
-
* diagnostic tool deep in that provider (e.g. device-manager's
|
|
6517
|
-
* fleet-read census) can read it without every provider method changing
|
|
6518
|
-
* signature, the same "extra field the provider destructures or
|
|
6519
|
-
* ignores" convention `nodeId` already uses on this path.
|
|
6520
|
-
*/
|
|
6521
|
-
async dispatch(route, method, args, addonId, callerAddonId) {
|
|
6522
|
-
try {
|
|
6523
|
-
return await this.dispatchInner(route, method, args, addonId, callerAddonId);
|
|
6524
|
-
} catch (err) {
|
|
6525
|
-
if (err instanceof CapRouteError) throw err;
|
|
6526
|
-
const nodeId = this.routeNodeId(route);
|
|
6527
|
-
const cause = err instanceof Error ? err : new Error(String(err));
|
|
6528
|
-
throw new CapRouteError(route.capName, method, {
|
|
6529
|
-
reason: "transport-failed",
|
|
6530
|
-
nodeId,
|
|
6531
|
-
rejected: [{
|
|
6532
|
-
kind: route.kind,
|
|
6533
|
-
why: cause.message
|
|
6534
|
-
}]
|
|
6535
|
-
}, cause);
|
|
6536
|
-
}
|
|
6537
|
-
}
|
|
6538
|
-
/**
|
|
6539
|
-
* Inner dispatch: may throw CapRouteError (validation failures) or arbitrary
|
|
6540
|
-
* transport errors. The outer `dispatch` wraps non-CapRouteErrors.
|
|
6541
|
-
*/
|
|
6542
|
-
async dispatchInner(route, method, args, addonId, callerAddonId) {
|
|
6543
|
-
switch (route.kind) {
|
|
6544
|
-
case "hub-in-process": return route.ref.invoke(method, withCallerAddonId(args, callerAddonId));
|
|
6545
|
-
case "hub-local-uds": {
|
|
6546
|
-
const registry = this.hubLocalRegistry;
|
|
6547
|
-
if (registry === null) throw new CapRouteError(route.capName, method, {
|
|
6548
|
-
reason: "no-provider",
|
|
6549
|
-
rejected: [{
|
|
6550
|
-
kind: "hub-local-uds",
|
|
6551
|
-
why: "UDS registry not available"
|
|
6552
|
-
}]
|
|
6553
|
-
});
|
|
6554
|
-
const deviceId = extractDeviceId(args);
|
|
6555
|
-
const input = {
|
|
6556
|
-
capName: route.capName,
|
|
6557
|
-
method,
|
|
6558
|
-
args,
|
|
6559
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
6560
|
-
};
|
|
6561
|
-
return registry.callCapOnChild(route.childId, input);
|
|
6562
|
-
}
|
|
6563
|
-
case "remote-moleculer": {
|
|
6564
|
-
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
6565
|
-
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
6566
|
-
reason: "no-provider",
|
|
6567
|
-
nodeId: route.nodeId,
|
|
6568
|
-
rejected: [{
|
|
6569
|
-
kind: "remote-moleculer",
|
|
6570
|
-
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
6571
|
-
}]
|
|
6572
|
-
});
|
|
6573
|
-
const deviceId = extractDeviceId(args);
|
|
6574
|
-
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
6575
|
-
const action = capActionName(addonId, route.capName, method, isNative);
|
|
6576
|
-
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
6577
|
-
nodeID: route.nodeId,
|
|
6578
|
-
timeout: this.callTimeout(route.capName, method)
|
|
6579
|
-
});
|
|
6580
|
-
}
|
|
6581
|
-
case "agent-child-forward": {
|
|
6582
|
-
const deviceId = extractDeviceId(args);
|
|
6583
|
-
const params = {
|
|
6584
|
-
capName: route.capName,
|
|
6585
|
-
method,
|
|
6586
|
-
args,
|
|
6587
|
-
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
6588
|
-
...deviceId !== void 0 ? { deviceId } : {},
|
|
6589
|
-
...addonId !== void 0 ? { addonId } : {}
|
|
6590
|
-
};
|
|
6591
|
-
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
6592
|
-
nodeID: route.agentNodeId,
|
|
6593
|
-
timeout: this.callTimeout(route.capName, method)
|
|
6594
|
-
});
|
|
6595
|
-
}
|
|
6596
|
-
}
|
|
6597
|
-
}
|
|
6598
|
-
buildSnapshot() {
|
|
6599
|
-
const hubLocalRegistry = this.hubLocalRegistry;
|
|
6600
|
-
const nodeAuthority = this.nodeAuthority;
|
|
6601
|
-
const inProcessProviders = this.inProcessProviders;
|
|
6602
|
-
const hubResidentProviders = this.hubResidentProviders;
|
|
6603
|
-
return {
|
|
6604
|
-
hubNodeId: this.hubNodeId,
|
|
6605
|
-
hubInProcessProvides: (cap) => inProcessProviders(cap) !== null,
|
|
6606
|
-
...hubResidentProviders !== void 0 ? {
|
|
6607
|
-
hubResidentProvides: (cap) => hubResidentProviders(cap) !== null,
|
|
6608
|
-
getHubResidentProviderRef: (cap) => hubResidentProviders(cap)
|
|
6609
|
-
} : {},
|
|
6610
|
-
hubLocalChildProvides: (cap, deviceId) => hubLocalRegistry !== null && hubLocalRegistry.resolveChildId(cap, deviceId) !== null,
|
|
6611
|
-
nodeKnowsCap: (nodeId, cap) => nodeAuthority.nodeKnowsCap(nodeId, cap),
|
|
6612
|
-
nodeIsAgent: (nodeId) => nodeAuthority.nodeIsAgent(nodeId),
|
|
6613
|
-
nodeOnline: (nodeId) => nodeAuthority.nodeOnline(nodeId),
|
|
6614
|
-
listKnownNodeIds: () => nodeAuthority.listNodeIds(),
|
|
6615
|
-
getInProcessProviderRef: (cap) => inProcessProviders(cap),
|
|
6616
|
-
getHubLocalChildId: (cap, deviceId) => hubLocalRegistry !== null ? hubLocalRegistry.resolveChildId(cap, deviceId) : null,
|
|
6617
|
-
getAgentChildId: (agentNodeId, cap) => nodeAuthority.getAgentChildId(agentNodeId, cap)
|
|
6618
|
-
};
|
|
6619
|
-
}
|
|
6620
|
-
/** Extract a nodeId string from a route for error reporting, or undefined. */
|
|
6621
|
-
routeNodeId(route) {
|
|
6622
|
-
switch (route.kind) {
|
|
6623
|
-
case "hub-in-process": return this.hubNodeId;
|
|
6624
|
-
case "hub-local-uds": return `${this.hubNodeId}/${route.childId}`;
|
|
6625
|
-
case "remote-moleculer": return route.nodeId;
|
|
6626
|
-
case "agent-child-forward": return route.agentNodeId;
|
|
6627
|
-
}
|
|
6628
|
-
}
|
|
6629
|
-
};
|
|
6630
|
-
//#endregion
|
|
6631
|
-
//#region src/kernel/transport/local-child-registry.ts
|
|
6632
|
-
function readFanoutMode() {
|
|
6633
|
-
const raw = process.env.CAMSTACK_UDS_EVENT_FANOUT;
|
|
6634
|
-
if (raw === "shadow" || raw === "broadcast") return raw;
|
|
6635
|
-
return "filter";
|
|
6636
|
-
}
|
|
6637
|
-
/**
|
|
6638
|
-
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
6639
|
-
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
6640
|
-
* prefix to distinguish routing failures (safe to retry via broker) from real
|
|
6641
|
-
* provider errors (must NOT retry to avoid double-executing side effects).
|
|
6642
|
-
*/
|
|
6643
|
-
var UDS_NO_ROUTE_PREFIX = "UDS_NO_ROUTE";
|
|
6644
|
-
/**
|
|
6645
|
-
* Parent-side authority for local addon-runners reachable over a
|
|
6646
|
-
* `LocalTransportServer` (UDS). Children register their cap manifest on
|
|
6647
|
-
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
6648
|
-
* owning child over the channel and drops a child's caps on disconnect.
|
|
6649
|
-
*/
|
|
6650
|
-
var LocalChildRegistry = class {
|
|
6651
|
-
children = /* @__PURE__ */ new Map();
|
|
6652
|
-
registeredHandler = () => {};
|
|
6653
|
-
goneHandler = () => {};
|
|
6654
|
-
/** Source of {@link RegisteredChild.incarnation}; one bump per accepted connection. */
|
|
6655
|
-
nextIncarnation = 1;
|
|
6656
|
-
eventHandler = null;
|
|
6657
|
-
logHandler = null;
|
|
6658
|
-
readinessHandler = null;
|
|
6659
|
-
server;
|
|
6660
|
-
onUnownedCall;
|
|
6661
|
-
logger;
|
|
6662
|
-
getActiveSingletonAddonId;
|
|
6663
|
-
resolveChildIdForAddon;
|
|
6664
|
-
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
6665
|
-
ownNodeId;
|
|
6666
|
-
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
6667
|
-
isAggregatedCollectionMethod;
|
|
6668
|
-
isAddonPinnedCall;
|
|
6669
|
-
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
6670
|
-
capTimeoutMs;
|
|
6671
|
-
/** See {@link LocalChildRegistryOptions.capUsageObserver}. */
|
|
6672
|
-
capUsageObserver;
|
|
6673
|
-
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
6674
|
-
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
6675
|
-
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
6676
|
-
fanoutMode = readFanoutMode();
|
|
6677
|
-
/** Per-child event fan-out counters (sent / suppressed). */
|
|
6678
|
-
childEventStats = /* @__PURE__ */ new Map();
|
|
6679
|
-
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
6680
|
-
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
6681
|
-
/**
|
|
6682
|
-
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
6683
|
-
* or a full `LocalChildRegistryOptions` object.
|
|
6684
|
-
*
|
|
6685
|
-
* Positional overloads (existing call sites are unchanged):
|
|
6686
|
-
* new LocalChildRegistry(server)
|
|
6687
|
-
* new LocalChildRegistry(server, onUnownedCall)
|
|
6688
|
-
*
|
|
6689
|
-
* Options object (new call sites that pass a logger):
|
|
6690
|
-
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
6691
|
-
*/
|
|
6692
|
-
constructor(serverOrOptions, onUnownedCallArg) {
|
|
6693
|
-
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
6694
|
-
const opts = serverOrOptions;
|
|
6695
|
-
this.server = opts.server;
|
|
6696
|
-
this.onUnownedCall = opts.onUnownedCall;
|
|
6697
|
-
this.logger = opts.logger;
|
|
6698
|
-
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
6699
|
-
this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
|
|
6700
|
-
this.ownNodeId = opts.ownNodeId;
|
|
6701
|
-
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
6702
|
-
this.isAddonPinnedCall = opts.isAddonPinnedCall;
|
|
6703
|
-
this.capTimeoutMs = opts.capTimeoutMs;
|
|
6704
|
-
this.capUsageObserver = opts.capUsageObserver;
|
|
6705
|
-
} else {
|
|
6706
|
-
this.server = serverOrOptions;
|
|
6707
|
-
this.onUnownedCall = onUnownedCallArg;
|
|
6708
|
-
}
|
|
6709
|
-
}
|
|
6710
|
-
async start() {
|
|
6711
|
-
this.logger?.info("UDS event fan-out mode", { mode: this.fanoutMode });
|
|
6712
|
-
this.server.onConnection((channel) => this.onConnection(channel));
|
|
6713
|
-
await this.server.listen();
|
|
6714
|
-
}
|
|
6715
|
-
/**
|
|
6716
|
-
* Per-child event fan-out counters (`{ sent, suppressed }`) or `null` if the
|
|
6717
|
-
* child has no recorded events yet. Exposed for the starvation-verification
|
|
6718
|
-
* surface (shadow burn-in + operator debug).
|
|
6719
|
-
*/
|
|
6720
|
-
getChildEventStats(childId) {
|
|
6721
|
-
const s = this.childEventStats.get(childId);
|
|
6722
|
-
return s === void 0 ? null : {
|
|
6723
|
-
sent: s.sent,
|
|
6724
|
-
suppressed: s.suppressed
|
|
6725
|
-
};
|
|
6726
|
-
}
|
|
6727
|
-
/**
|
|
6728
|
-
* This child's socket traffic — bytes and messages, both directions, split by
|
|
6729
|
-
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
6730
|
-
* counters (an in-process or test transport).
|
|
6731
|
-
*
|
|
6732
|
-
* The event counters above cover ONE kind of frame and, once surfaced on
|
|
6733
|
-
* 2026-08-29, accounted for 4% of hub-main's ~44 000 socket syscalls/s. This
|
|
6734
|
-
* is the same question asked of every frame the channel moves, and it is the
|
|
6735
|
-
* only per-peer attribution that exists: `/proc` reports one `rchar`/`wchar`
|
|
6736
|
-
* pair for the whole process and has no per-socket breakdown.
|
|
6737
|
-
*/
|
|
6738
|
-
getChildSocketTraffic(childId) {
|
|
6739
|
-
const entry = this.children.get(childId);
|
|
6740
|
-
if (entry === void 0) return null;
|
|
6741
|
-
return entry.channel.readTraffic?.() ?? null;
|
|
6742
|
-
}
|
|
6743
|
-
/**
|
|
6744
|
-
* The regime the counters above were produced under, read once from
|
|
6745
|
-
* `CAMSTACK_UDS_EVENT_FANOUT` at construction.
|
|
6746
|
-
*
|
|
6747
|
-
* A suppressed count means "nothing WAS dropped" under `filter` and "nothing
|
|
6748
|
-
* WOULD have been dropped" under `shadow`. Those are different claims, and
|
|
6749
|
-
* until this getter existed the mode was invisible at runtime — the counters
|
|
6750
|
-
* were readable and the regime that produced them was not.
|
|
6751
|
-
*/
|
|
6752
|
-
get eventFanoutMode() {
|
|
6753
|
-
return this.fanoutMode;
|
|
6780
|
+
get eventFanoutMode() {
|
|
6781
|
+
return this.fanoutMode;
|
|
6754
6782
|
}
|
|
6755
6783
|
/**
|
|
6756
6784
|
* Connected children that never declared a subscription pattern set.
|
|
@@ -7118,7 +7146,7 @@ var LocalChildRegistry = class {
|
|
|
7118
7146
|
return;
|
|
7119
7147
|
}
|
|
7120
7148
|
});
|
|
7121
|
-
channel.onRequest(async (body) => {
|
|
7149
|
+
channel.onRequest(async (body, payload) => {
|
|
7122
7150
|
const msg = body;
|
|
7123
7151
|
if (msg.kind === "register") {
|
|
7124
7152
|
if (childId !== null && childId !== msg.childId) throw new Error(`child attempted to change identity from "${childId}" to "${msg.childId}"`);
|
|
@@ -7135,7 +7163,8 @@ var LocalChildRegistry = class {
|
|
|
7135
7163
|
channel,
|
|
7136
7164
|
caps: msg.caps,
|
|
7137
7165
|
eventPatterns,
|
|
7138
|
-
incarnation
|
|
7166
|
+
incarnation,
|
|
7167
|
+
rawForward: msg.rawForward === true
|
|
7139
7168
|
});
|
|
7140
7169
|
this.logPatternSet(msg.childId, eventPatterns);
|
|
7141
7170
|
this.registeredHandler({
|
|
@@ -7144,23 +7173,28 @@ var LocalChildRegistry = class {
|
|
|
7144
7173
|
incarnation,
|
|
7145
7174
|
...msg.customActions !== void 0 ? { customActions: msg.customActions } : {}
|
|
7146
7175
|
});
|
|
7147
|
-
return {
|
|
7148
|
-
|
|
7176
|
+
return {
|
|
7177
|
+
ok: true,
|
|
7178
|
+
rawForward: true
|
|
7179
|
+
};
|
|
7180
|
+
}
|
|
7149
7181
|
if (msg.kind === "cap-call-out") {
|
|
7150
7182
|
const out = msg;
|
|
7151
|
-
const
|
|
7183
|
+
const hints = out.hints ?? liftRoutingHints(out.args);
|
|
7184
|
+
const materialise = () => payload !== void 0 ? unpackPayload(payload) : out.args;
|
|
7185
|
+
const buildInput = () => ({
|
|
7152
7186
|
capName: out.capName,
|
|
7153
7187
|
method: out.method,
|
|
7154
|
-
args:
|
|
7188
|
+
args: materialise(),
|
|
7155
7189
|
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
7156
7190
|
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {},
|
|
7157
7191
|
...out.native === true ? { native: true } : {},
|
|
7158
7192
|
...childId !== null ? { callerAddonId: childId } : {}
|
|
7159
|
-
};
|
|
7160
|
-
const pinnedNodeId = out.nodeId ??
|
|
7193
|
+
});
|
|
7194
|
+
const pinnedNodeId = out.nodeId ?? hints.nodeId;
|
|
7161
7195
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
7162
7196
|
const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
|
|
7163
|
-
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method,
|
|
7197
|
+
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, hints) === true;
|
|
7164
7198
|
const target = !(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null;
|
|
7165
7199
|
this.recordCapUsage(childId, target, out.capName, out.method);
|
|
7166
7200
|
if (target !== null) {
|
|
@@ -7168,9 +7202,27 @@ var LocalChildRegistry = class {
|
|
|
7168
7202
|
this.egressRoutedCaps.add(out.capName);
|
|
7169
7203
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
7170
7204
|
}
|
|
7171
|
-
|
|
7205
|
+
const entry = this.children.get(target);
|
|
7206
|
+
if (payload !== void 0 && entry !== void 0 && entry.rawForward) {
|
|
7207
|
+
this.forwardedRaw += 1;
|
|
7208
|
+
const forward = {
|
|
7209
|
+
kind: "cap-call",
|
|
7210
|
+
capName: out.capName,
|
|
7211
|
+
method: out.method,
|
|
7212
|
+
args: void 0,
|
|
7213
|
+
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
|
|
7214
|
+
};
|
|
7215
|
+
const declared = this.capTimeoutMs?.(out.capName, out.method);
|
|
7216
|
+
return entry.channel.request(forward, {
|
|
7217
|
+
...typeof declared === "number" ? { timeoutMs: declared } : {},
|
|
7218
|
+
payload
|
|
7219
|
+
});
|
|
7220
|
+
}
|
|
7221
|
+
this.forwardedDecoded += 1;
|
|
7222
|
+
return this.callCap(buildInput());
|
|
7172
7223
|
}
|
|
7173
|
-
|
|
7224
|
+
this.forwardedDecoded += 1;
|
|
7225
|
+
if (this.onUnownedCall !== void 0) return this.onUnownedCall(buildInput());
|
|
7174
7226
|
throw new Error(`${UDS_NO_ROUTE_PREFIX}: cap-call-out has no local provider for "${out.capName}" and no fallback`);
|
|
7175
7227
|
}
|
|
7176
7228
|
if (msg.kind === "readiness-request") return {
|
|
@@ -7200,6 +7252,11 @@ var LocalChildRegistry = class {
|
|
|
7200
7252
|
};
|
|
7201
7253
|
//#endregion
|
|
7202
7254
|
//#region src/kernel/transport/local-child-client.ts
|
|
7255
|
+
/** Did the parent's `register` answer say it accepts payload-borne args? */
|
|
7256
|
+
function ackAcceptsRawForward(ack) {
|
|
7257
|
+
if (ack === null || typeof ack !== "object") return false;
|
|
7258
|
+
return ack.rawForward === true;
|
|
7259
|
+
}
|
|
7203
7260
|
/**
|
|
7204
7261
|
* Child side of the local UDS transport. Connects to its parent (hub or
|
|
7205
7262
|
* agent), registers its cap manifest, and serves parent→child cap calls by
|
|
@@ -7255,6 +7312,13 @@ var LocalChildClient = class {
|
|
|
7255
7312
|
latestCustomActions = null;
|
|
7256
7313
|
/** Events and logs queued while the channel is not yet open. */
|
|
7257
7314
|
pendingEmits = [];
|
|
7315
|
+
/**
|
|
7316
|
+
* Whether the parent acknowledged raw-forward at `register`. Until it has,
|
|
7317
|
+
* `callOut` sends args inline exactly as every child always did — a legacy
|
|
7318
|
+
* parent would decode a payload-borne call to `args: undefined` and run the
|
|
7319
|
+
* provider on nothing, silently. Re-read on every re-register.
|
|
7320
|
+
*/
|
|
7321
|
+
parentRawForward = false;
|
|
7258
7322
|
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
7259
7323
|
eventHandler = null;
|
|
7260
7324
|
/**
|
|
@@ -7362,15 +7426,20 @@ var LocalChildClient = class {
|
|
|
7362
7426
|
if (this.client !== null) throw new Error("LocalChildClient: already started — call close() first");
|
|
7363
7427
|
const client = createLocalTransport().createClient(this.options.nodeId);
|
|
7364
7428
|
const channel = await client.connect();
|
|
7365
|
-
channel.onRequest(async (body) => {
|
|
7429
|
+
channel.onRequest(async (body, payload) => {
|
|
7366
7430
|
const msg = body;
|
|
7367
|
-
if (msg.kind === "cap-call")
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7431
|
+
if (msg.kind === "cap-call") {
|
|
7432
|
+
const args = payload !== void 0 ? unpackPayload(payload) : msg.args;
|
|
7433
|
+
const result = await this.options.dispatch({
|
|
7434
|
+
capName: msg.capName,
|
|
7435
|
+
method: msg.method,
|
|
7436
|
+
args,
|
|
7437
|
+
...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {},
|
|
7438
|
+
...msg.addonId !== void 0 ? { addonId: msg.addonId } : {}
|
|
7439
|
+
});
|
|
7440
|
+
if (payload !== void 0 && result !== void 0) return new PayloadReply(void 0, packPayload(result));
|
|
7441
|
+
return result;
|
|
7442
|
+
}
|
|
7374
7443
|
if (msg.kind === "addon-call") {
|
|
7375
7444
|
if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
|
|
7376
7445
|
return this.addonCallHandler({
|
|
@@ -7394,7 +7463,7 @@ var LocalChildClient = class {
|
|
|
7394
7463
|
});
|
|
7395
7464
|
const register = this.registerFrame(this.latestCaps);
|
|
7396
7465
|
try {
|
|
7397
|
-
await channel.request(register);
|
|
7466
|
+
this.parentRawForward = ackAcceptsRawForward(await channel.request(register));
|
|
7398
7467
|
} catch (err) {
|
|
7399
7468
|
await client.close();
|
|
7400
7469
|
throw err;
|
|
@@ -7404,105 +7473,564 @@ var LocalChildClient = class {
|
|
|
7404
7473
|
this.flushPending(channel);
|
|
7405
7474
|
for (const cb of this.connectedHandlers) cb();
|
|
7406
7475
|
}
|
|
7407
|
-
/** Flush buffered pre-start emits over the now-open channel. */
|
|
7408
|
-
flushPending(channel) {
|
|
7409
|
-
for (const item of this.pendingEmits) channel.emit(item.msg);
|
|
7410
|
-
this.pendingEmits.length = 0;
|
|
7476
|
+
/** Flush buffered pre-start emits over the now-open channel. */
|
|
7477
|
+
flushPending(channel) {
|
|
7478
|
+
for (const item of this.pendingEmits) channel.emit(item.msg);
|
|
7479
|
+
this.pendingEmits.length = 0;
|
|
7480
|
+
}
|
|
7481
|
+
/**
|
|
7482
|
+
* Re-send the cap manifest to the parent, atomically replacing the child's
|
|
7483
|
+
* registered descriptor set. Call this after device-restore so newly
|
|
7484
|
+
* registered native (device-scoped) caps become routable over UDS.
|
|
7485
|
+
*
|
|
7486
|
+
* Safe to call before `start()`: the new set is buffered and `start()` sends
|
|
7487
|
+
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
7488
|
+
* the set is sent immediately.
|
|
7489
|
+
*/
|
|
7490
|
+
async updateCaps(caps, customActions) {
|
|
7491
|
+
this.latestCaps = caps;
|
|
7492
|
+
if (customActions !== void 0) this.latestCustomActions = customActions;
|
|
7493
|
+
if (this.channel === null) return;
|
|
7494
|
+
this.parentRawForward = ackAcceptsRawForward(await this.channel.request(this.registerFrame(caps)));
|
|
7495
|
+
}
|
|
7496
|
+
/**
|
|
7497
|
+
* The register frame: caps, plus everything a re-register must atomically
|
|
7498
|
+
* re-carry so no separate re-sync can be missed — the subscription union
|
|
7499
|
+
* (omitted if nothing ever declared, which keeps the fail-open shape) and the
|
|
7500
|
+
* custom-action catalogs (omitted until the init loop produced them).
|
|
7501
|
+
*/
|
|
7502
|
+
registerFrame(caps) {
|
|
7503
|
+
return {
|
|
7504
|
+
kind: "register",
|
|
7505
|
+
childId: this.options.childId,
|
|
7506
|
+
caps,
|
|
7507
|
+
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {},
|
|
7508
|
+
...this.latestCustomActions !== null ? { customActions: this.latestCustomActions } : {},
|
|
7509
|
+
rawForward: true
|
|
7510
|
+
};
|
|
7511
|
+
}
|
|
7512
|
+
/**
|
|
7513
|
+
* Fire-and-forget: send a system event to the parent for forwarding to the
|
|
7514
|
+
* hub event bus. Safe to call before `start()` — events are buffered and
|
|
7515
|
+
* flushed on connect.
|
|
7516
|
+
*/
|
|
7517
|
+
emitEvent(event) {
|
|
7518
|
+
const msg = {
|
|
7519
|
+
kind: "event",
|
|
7520
|
+
event
|
|
7521
|
+
};
|
|
7522
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
7523
|
+
else this.pendingEmits.push({
|
|
7524
|
+
kind: "event",
|
|
7525
|
+
msg
|
|
7526
|
+
});
|
|
7527
|
+
}
|
|
7528
|
+
/**
|
|
7529
|
+
* Fire-and-forget: send a structured log entry to the parent. Safe to call
|
|
7530
|
+
* before `start()` — log entries are buffered and flushed on connect.
|
|
7531
|
+
*/
|
|
7532
|
+
sendLog(entry) {
|
|
7533
|
+
const msg = {
|
|
7534
|
+
kind: "log",
|
|
7535
|
+
...entry
|
|
7536
|
+
};
|
|
7537
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
7538
|
+
else this.pendingEmits.push({
|
|
7539
|
+
kind: "log",
|
|
7540
|
+
msg
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* Request the current readiness snapshot from the parent. Returns the
|
|
7545
|
+
* authoritative set of `IReadinessRegistryRecord` entries the hub holds.
|
|
7546
|
+
* Requires `start()` to have been called; throws with a clear message if not.
|
|
7547
|
+
*/
|
|
7548
|
+
async requestReadinessSnapshot() {
|
|
7549
|
+
if (this.channel === null) throw new Error("LocalChildClient: requestReadinessSnapshot called before start()");
|
|
7550
|
+
return (await this.channel.request({ kind: "readiness-request" })).records;
|
|
7551
|
+
}
|
|
7552
|
+
/**
|
|
7553
|
+
* Ask the parent to execute a cap call this child does NOT own. The parent
|
|
7554
|
+
* routes to the owning local sibling over UDS, or — if no sibling owns it —
|
|
7555
|
+
* to its `onUnownedCall` fallback (the cluster CapabilityRegistry in
|
|
7556
|
+
* production). Throws if called before `start()`.
|
|
7557
|
+
*/
|
|
7558
|
+
async callOut(input) {
|
|
7559
|
+
if (this.channel === null) throw new Error("LocalChildClient: callOut before start");
|
|
7560
|
+
const routing = {
|
|
7561
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
7562
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
7563
|
+
...input.native === true ? { native: true } : {}
|
|
7564
|
+
};
|
|
7565
|
+
if (this.parentRawForward && input.args !== void 0) {
|
|
7566
|
+
const hints = liftRoutingHints(input.args);
|
|
7567
|
+
const msg = {
|
|
7568
|
+
kind: "cap-call-out",
|
|
7569
|
+
capName: input.capName,
|
|
7570
|
+
method: input.method,
|
|
7571
|
+
...Object.keys(hints).length > 0 ? { hints } : {},
|
|
7572
|
+
...routing
|
|
7573
|
+
};
|
|
7574
|
+
const reply = await this.channel.request(msg, { payload: packPayload(input.args) });
|
|
7575
|
+
return reply instanceof PayloadReply ? unpackPayload(reply.payload) : reply;
|
|
7576
|
+
}
|
|
7577
|
+
const msg = {
|
|
7578
|
+
kind: "cap-call-out",
|
|
7579
|
+
capName: input.capName,
|
|
7580
|
+
method: input.method,
|
|
7581
|
+
args: input.args,
|
|
7582
|
+
...routing
|
|
7583
|
+
};
|
|
7584
|
+
return this.channel.request(msg);
|
|
7585
|
+
}
|
|
7586
|
+
/** Disconnect from the parent. Safe to call before `start()` (no-op) and idempotent. */
|
|
7587
|
+
async close() {
|
|
7588
|
+
await this.client?.close();
|
|
7589
|
+
this.client = null;
|
|
7590
|
+
this.channel = null;
|
|
7591
|
+
}
|
|
7592
|
+
};
|
|
7593
|
+
//#endregion
|
|
7594
|
+
//#region src/kernel/transport/cap-route.ts
|
|
7595
|
+
function buildMessage(capName, method, detail) {
|
|
7596
|
+
const call = method !== void 0 ? `${capName}.${method}` : capName;
|
|
7597
|
+
const target = detail.nodeId !== void 0 ? ` to node ${detail.nodeId}` : "";
|
|
7598
|
+
const rejectedStr = detail.rejected.map((r) => `${r.kind}=${r.why}`).join("; ");
|
|
7599
|
+
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
7600
|
+
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
7601
|
+
}
|
|
7602
|
+
var CapRouteError = class extends Error {
|
|
7603
|
+
reason;
|
|
7604
|
+
nodeId;
|
|
7605
|
+
rejected;
|
|
7606
|
+
/**
|
|
7607
|
+
* @param cause Optional original error that triggered this routing failure.
|
|
7608
|
+
* Stored as `Error.cause` (TC39 standard option, Node 16.9+).
|
|
7609
|
+
* Dispatchers wrapping transport errors MUST pass the original.
|
|
7610
|
+
*/
|
|
7611
|
+
constructor(capName, method, detail, cause) {
|
|
7612
|
+
super(buildMessage(capName, method, detail), cause !== void 0 ? { cause } : void 0);
|
|
7613
|
+
this.name = "CapRouteError";
|
|
7614
|
+
this.reason = detail.reason;
|
|
7615
|
+
this.nodeId = detail.nodeId;
|
|
7616
|
+
this.rejected = detail.rejected;
|
|
7617
|
+
}
|
|
7618
|
+
};
|
|
7619
|
+
/**
|
|
7620
|
+
* Classifies a (capName, opts) pair into a typed CapRoute dispatch descriptor.
|
|
7621
|
+
*
|
|
7622
|
+
* Precedence (explicit nodeId path):
|
|
7623
|
+
* 1. hub-in-process — hub node + hubInProcessProvides
|
|
7624
|
+
* 2. hub-local-uds — hub node + hubLocalChildProvides
|
|
7625
|
+
* 3. node-offline → CapRouteError{reason:'node-offline'}
|
|
7626
|
+
* 4. agent-child-forward — node is an agent AND nodeKnowsCap
|
|
7627
|
+
* 5. remote-moleculer — any other online non-agent node
|
|
7628
|
+
*
|
|
7629
|
+
* Singleton path (no nodeId):
|
|
7630
|
+
* 1. hub-in-process (hub provides in-process)
|
|
7631
|
+
* 2. hub-local-uds (a hub-local UDS child provides it)
|
|
7632
|
+
* 3. remote-moleculer (any online, non-hub node that knows it)
|
|
7633
|
+
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
7634
|
+
*
|
|
7635
|
+
* PURE: no side effects, no async, no broker/registry imports.
|
|
7636
|
+
*/
|
|
7637
|
+
function classifyCapRoute(capName, opts, snapshot) {
|
|
7638
|
+
const rejected = [];
|
|
7639
|
+
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
7640
|
+
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
7641
|
+
}
|
|
7642
|
+
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
7643
|
+
if (nodeId === snap.hubNodeId) {
|
|
7644
|
+
const residentProvides = snap.hubResidentProvides;
|
|
7645
|
+
const providesOnHub = residentProvides ?? snap.hubInProcessProvides;
|
|
7646
|
+
const getRef = residentProvides !== void 0 ? snap.getHubResidentProviderRef : snap.getInProcessProviderRef;
|
|
7647
|
+
if (providesOnHub(capName)) {
|
|
7648
|
+
const ref = getRef?.(capName) ?? null;
|
|
7649
|
+
if (ref !== null) return {
|
|
7650
|
+
kind: "hub-in-process",
|
|
7651
|
+
capName,
|
|
7652
|
+
ref
|
|
7653
|
+
};
|
|
7654
|
+
rejected.push({
|
|
7655
|
+
kind: "hub-in-process",
|
|
7656
|
+
why: "provider ref not available in snapshot"
|
|
7657
|
+
});
|
|
7658
|
+
throw new CapRouteError(capName, void 0, {
|
|
7659
|
+
reason: "no-provider",
|
|
7660
|
+
nodeId,
|
|
7661
|
+
rejected
|
|
7662
|
+
});
|
|
7663
|
+
}
|
|
7664
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
7665
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7666
|
+
if (childId !== null) return {
|
|
7667
|
+
kind: "hub-local-uds",
|
|
7668
|
+
capName,
|
|
7669
|
+
childId
|
|
7670
|
+
};
|
|
7671
|
+
rejected.push({
|
|
7672
|
+
kind: "hub-local-uds",
|
|
7673
|
+
why: "child id not resolvable from snapshot"
|
|
7674
|
+
});
|
|
7675
|
+
throw new CapRouteError(capName, void 0, {
|
|
7676
|
+
reason: "no-provider",
|
|
7677
|
+
nodeId,
|
|
7678
|
+
rejected
|
|
7679
|
+
});
|
|
7680
|
+
}
|
|
7681
|
+
rejected.push({
|
|
7682
|
+
kind: "hub-in-process",
|
|
7683
|
+
why: "hub does not provide this cap in-process"
|
|
7684
|
+
});
|
|
7685
|
+
rejected.push({
|
|
7686
|
+
kind: "hub-local-uds",
|
|
7687
|
+
why: "no hub-local child provides this cap"
|
|
7688
|
+
});
|
|
7689
|
+
throw new CapRouteError(capName, void 0, {
|
|
7690
|
+
reason: "no-provider",
|
|
7691
|
+
nodeId,
|
|
7692
|
+
rejected
|
|
7693
|
+
});
|
|
7694
|
+
}
|
|
7695
|
+
if (!snap.nodeOnline(nodeId)) {
|
|
7696
|
+
rejected.push({
|
|
7697
|
+
kind: "remote-moleculer",
|
|
7698
|
+
why: `node ${nodeId} is offline`
|
|
7699
|
+
});
|
|
7700
|
+
throw new CapRouteError(capName, void 0, {
|
|
7701
|
+
reason: "node-offline",
|
|
7702
|
+
nodeId,
|
|
7703
|
+
rejected
|
|
7704
|
+
});
|
|
7705
|
+
}
|
|
7706
|
+
if (snap.nodeIsAgent(nodeId)) {
|
|
7707
|
+
if (snap.nodeKnowsCap(nodeId, capName)) return {
|
|
7708
|
+
kind: "agent-child-forward",
|
|
7709
|
+
capName,
|
|
7710
|
+
agentNodeId: nodeId,
|
|
7711
|
+
childId: snap.getAgentChildId?.(nodeId, capName) ?? void 0
|
|
7712
|
+
};
|
|
7713
|
+
rejected.push({
|
|
7714
|
+
kind: "agent-child-forward",
|
|
7715
|
+
why: `agent ${nodeId} does not know cap ${capName}`
|
|
7716
|
+
});
|
|
7717
|
+
throw new CapRouteError(capName, void 0, {
|
|
7718
|
+
reason: "no-provider",
|
|
7719
|
+
nodeId,
|
|
7720
|
+
rejected
|
|
7721
|
+
});
|
|
7722
|
+
}
|
|
7723
|
+
return {
|
|
7724
|
+
kind: "remote-moleculer",
|
|
7725
|
+
capName,
|
|
7726
|
+
nodeId
|
|
7727
|
+
};
|
|
7728
|
+
}
|
|
7729
|
+
function classifySingleton(capName, deviceId, snap, rejected) {
|
|
7730
|
+
if (snap.hubInProcessProvides(capName)) {
|
|
7731
|
+
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
7732
|
+
if (ref !== null) return {
|
|
7733
|
+
kind: "hub-in-process",
|
|
7734
|
+
capName,
|
|
7735
|
+
ref
|
|
7736
|
+
};
|
|
7737
|
+
rejected.push({
|
|
7738
|
+
kind: "hub-in-process",
|
|
7739
|
+
why: "provider ref not available in snapshot"
|
|
7740
|
+
});
|
|
7741
|
+
} else rejected.push({
|
|
7742
|
+
kind: "hub-in-process",
|
|
7743
|
+
why: "hub does not provide this cap in-process"
|
|
7744
|
+
});
|
|
7745
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
7746
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7747
|
+
if (childId !== null) return {
|
|
7748
|
+
kind: "hub-local-uds",
|
|
7749
|
+
capName,
|
|
7750
|
+
childId
|
|
7751
|
+
};
|
|
7752
|
+
rejected.push({
|
|
7753
|
+
kind: "hub-local-uds",
|
|
7754
|
+
why: "child id not resolvable from snapshot"
|
|
7755
|
+
});
|
|
7756
|
+
} else rejected.push({
|
|
7757
|
+
kind: "hub-local-uds",
|
|
7758
|
+
why: "no hub-local child provides this cap"
|
|
7759
|
+
});
|
|
7760
|
+
const knownNodes = snap.listKnownNodeIds?.() ?? [];
|
|
7761
|
+
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
7762
|
+
kind: "remote-moleculer",
|
|
7763
|
+
capName,
|
|
7764
|
+
nodeId
|
|
7765
|
+
};
|
|
7766
|
+
rejected.push({
|
|
7767
|
+
kind: "remote-moleculer",
|
|
7768
|
+
why: "no online remote node knows this cap"
|
|
7769
|
+
});
|
|
7770
|
+
throw new CapRouteError(capName, void 0, {
|
|
7771
|
+
reason: "no-provider",
|
|
7772
|
+
rejected
|
|
7773
|
+
});
|
|
7774
|
+
}
|
|
7775
|
+
//#endregion
|
|
7776
|
+
//#region src/kernel/moleculer/resilient-cap-call.ts
|
|
7777
|
+
/** Moleculer error `type` values meaning "the service is not (yet) routable". */
|
|
7778
|
+
var DISCOVERY_ERROR_TYPES = new Set(["SERVICE_NOT_FOUND", "SERVICE_NOT_AVAILABLE"]);
|
|
7779
|
+
/** Default ceiling for the discovery wait — fail-fast past this. */
|
|
7780
|
+
var DEFAULT_DISCOVERY_TIMEOUT_MS$1 = 3e4;
|
|
7781
|
+
function isDiscoveryError(err) {
|
|
7782
|
+
if (typeof err !== "object" || err === null) return false;
|
|
7783
|
+
const type = err.type;
|
|
7784
|
+
return typeof type === "string" && DISCOVERY_ERROR_TYPES.has(type);
|
|
7785
|
+
}
|
|
7786
|
+
/**
|
|
7787
|
+
* Call a Moleculer action; on a service-discovery error, wait for the
|
|
7788
|
+
* named service to be discovered and retry the call exactly once.
|
|
7789
|
+
*/
|
|
7790
|
+
async function callWithServiceDiscovery(broker, serviceName, action, params, opts, discoveryTimeoutMs = DEFAULT_DISCOVERY_TIMEOUT_MS$1) {
|
|
7791
|
+
try {
|
|
7792
|
+
return await broker.call(action, params, opts);
|
|
7793
|
+
} catch (err) {
|
|
7794
|
+
if (!isDiscoveryError(err)) throw err;
|
|
7795
|
+
await broker.waitForServices([serviceName], discoveryTimeoutMs);
|
|
7796
|
+
return await broker.call(action, params, opts);
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
//#endregion
|
|
7800
|
+
//#region src/kernel/transport/cap-route-resolver.ts
|
|
7801
|
+
/** The Moleculer service name that Task 6's agent registers. */
|
|
7802
|
+
var AGENT_CAP_FWD_SERVICE = "$agent-cap-fwd";
|
|
7803
|
+
/** The Moleculer action (service.action) for agent cap forwarding. */
|
|
7804
|
+
var AGENT_CAP_FWD_ACTION = `${AGENT_CAP_FWD_SERVICE}.forward`;
|
|
7805
|
+
/** Default timeout for remote Moleculer cap calls (ms). */
|
|
7806
|
+
var REMOTE_CALL_TIMEOUT_MS = 6e4;
|
|
7807
|
+
function extractDeviceId(args) {
|
|
7808
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
7809
|
+
const raw = Reflect.get(args, "deviceId");
|
|
7810
|
+
return typeof raw === "number" ? raw : void 0;
|
|
7811
|
+
}
|
|
7812
|
+
/**
|
|
7813
|
+
* Extract an inline `nodeId` string from a cap call's args — the per-call node
|
|
7814
|
+
* pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
|
|
7815
|
+
* device-scoped caps carry `deviceId`, and the way the generated cap-router on
|
|
7816
|
+
* the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
|
|
7817
|
+
*
|
|
7818
|
+
* This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
|
|
7819
|
+
* benchmark addon running a synthetic/decoder workload ON a chosen agent). The
|
|
7820
|
+
* out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
|
|
7821
|
+
* hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
|
|
7822
|
+
* methods that don't declare `nodeId` simply ignore the extra field (they
|
|
7823
|
+
* destructure the fields they need); methods that DO declare it (e.g.
|
|
7824
|
+
* `getEngineProvisioning({nodeId})`) still receive it unchanged.
|
|
7825
|
+
*/
|
|
7826
|
+
function extractNodeId(args) {
|
|
7827
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
7828
|
+
const raw = Reflect.get(args, "nodeId");
|
|
7829
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
7830
|
+
}
|
|
7831
|
+
/**
|
|
7832
|
+
* Merge `callerAddonId` into `args` as an extra field — the SAME "provider
|
|
7833
|
+
* destructures what it needs and ignores the rest" convention
|
|
7834
|
+
* {@link extractNodeId} already relies on for the inline `nodeId` pin.
|
|
7835
|
+
* `undefined` (the overwhelmingly common case: no caller hint, or the census
|
|
7836
|
+
* consuming it is disarmed) returns `args` unchanged — no allocation. A
|
|
7837
|
+
* non-object `args` (or `null`/array) is returned unchanged too: there is
|
|
7838
|
+
* nowhere to attach an extra key without changing the payload's shape, and a
|
|
7839
|
+
* provider taking a bare value never reads `callerAddonId` off it anyway.
|
|
7840
|
+
*/
|
|
7841
|
+
function withCallerAddonId(args, callerAddonId) {
|
|
7842
|
+
if (callerAddonId === void 0) return args;
|
|
7843
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return args;
|
|
7844
|
+
return {
|
|
7845
|
+
...args,
|
|
7846
|
+
callerAddonId
|
|
7847
|
+
};
|
|
7848
|
+
}
|
|
7849
|
+
var CapRouteResolver = class {
|
|
7850
|
+
hubNodeId;
|
|
7851
|
+
broker;
|
|
7852
|
+
hubLocalRegistry;
|
|
7853
|
+
nodeAuthority;
|
|
7854
|
+
inProcessProviders;
|
|
7855
|
+
hubResidentProviders;
|
|
7856
|
+
capTimeoutMs;
|
|
7857
|
+
snapshot;
|
|
7858
|
+
constructor(deps) {
|
|
7859
|
+
this.hubNodeId = deps.hubNodeId;
|
|
7860
|
+
this.broker = deps.broker;
|
|
7861
|
+
this.hubLocalRegistry = deps.hubLocalRegistry;
|
|
7862
|
+
this.nodeAuthority = deps.nodeAuthority;
|
|
7863
|
+
this.inProcessProviders = deps.inProcessProviders;
|
|
7864
|
+
this.hubResidentProviders = deps.hubResidentProviders;
|
|
7865
|
+
this.capTimeoutMs = deps.capTimeoutMs;
|
|
7866
|
+
this.snapshot = this.buildSnapshot();
|
|
7867
|
+
}
|
|
7868
|
+
/** Per-call RPC timeout: the cap method's declared override, else the default. */
|
|
7869
|
+
callTimeout(capName, method) {
|
|
7870
|
+
return this.capTimeoutMs?.(capName, method) ?? REMOTE_CALL_TIMEOUT_MS;
|
|
7411
7871
|
}
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
* registered descriptor set. Call this after device-restore so newly
|
|
7415
|
-
* registered native (device-scoped) caps become routable over UDS.
|
|
7416
|
-
*
|
|
7417
|
-
* Safe to call before `start()`: the new set is buffered and `start()` sends
|
|
7418
|
-
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
7419
|
-
* the set is sent immediately.
|
|
7420
|
-
*/
|
|
7421
|
-
async updateCaps(caps, customActions) {
|
|
7422
|
-
this.latestCaps = caps;
|
|
7423
|
-
if (customActions !== void 0) this.latestCustomActions = customActions;
|
|
7424
|
-
if (this.channel === null) return;
|
|
7425
|
-
await this.channel.request(this.registerFrame(caps));
|
|
7872
|
+
resolveCapRoute(capName, opts) {
|
|
7873
|
+
return classifyCapRoute(capName, opts, this.snapshot);
|
|
7426
7874
|
}
|
|
7427
7875
|
/**
|
|
7428
|
-
*
|
|
7429
|
-
*
|
|
7430
|
-
*
|
|
7431
|
-
*
|
|
7876
|
+
* Resolve the hub-local-uds route for a cap owned by a forked hub-local
|
|
7877
|
+
* child, IGNORING any in-hub provider registered for the same cap name.
|
|
7878
|
+
*
|
|
7879
|
+
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
7880
|
+
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
7881
|
+
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
7882
|
+
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
7883
|
+
* active provider), but WRONG for the native-cap fallback
|
|
7884
|
+
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
7885
|
+
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
7886
|
+
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
7887
|
+
* itself, the native is never invoked, and the wrapper silently falls
|
|
7888
|
+
* through to its secondary strategy.
|
|
7889
|
+
*
|
|
7890
|
+
* This method consults ONLY the hub-local-child authority
|
|
7891
|
+
* (`hubLocalChildProvides` + `getHubLocalChildId`, both deviceId-aware), so
|
|
7892
|
+
* it returns the native child route regardless of any in-process shadow.
|
|
7893
|
+
* Returns null when no hub-local child owns `(capName, deviceId)` — the
|
|
7894
|
+
* caller then falls through to its remote-resolution branch.
|
|
7432
7895
|
*/
|
|
7433
|
-
|
|
7896
|
+
resolveHubLocalUdsRoute(capName, deviceId) {
|
|
7897
|
+
if (!this.snapshot.hubLocalChildProvides(capName, deviceId)) return null;
|
|
7898
|
+
const childId = this.snapshot.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7899
|
+
if (childId === null) return null;
|
|
7434
7900
|
return {
|
|
7435
|
-
kind: "
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {},
|
|
7439
|
-
...this.latestCustomActions !== null ? { customActions: this.latestCustomActions } : {}
|
|
7440
|
-
};
|
|
7441
|
-
}
|
|
7442
|
-
/**
|
|
7443
|
-
* Fire-and-forget: send a system event to the parent for forwarding to the
|
|
7444
|
-
* hub event bus. Safe to call before `start()` — events are buffered and
|
|
7445
|
-
* flushed on connect.
|
|
7446
|
-
*/
|
|
7447
|
-
emitEvent(event) {
|
|
7448
|
-
const msg = {
|
|
7449
|
-
kind: "event",
|
|
7450
|
-
event
|
|
7901
|
+
kind: "hub-local-uds",
|
|
7902
|
+
capName,
|
|
7903
|
+
childId
|
|
7451
7904
|
};
|
|
7452
|
-
if (this.channel !== null) this.channel.emit(msg);
|
|
7453
|
-
else this.pendingEmits.push({
|
|
7454
|
-
kind: "event",
|
|
7455
|
-
msg
|
|
7456
|
-
});
|
|
7457
7905
|
}
|
|
7458
7906
|
/**
|
|
7459
|
-
*
|
|
7460
|
-
*
|
|
7907
|
+
* @param addonId Optional out-of-band PROVIDER-selection hint (Task 7.5a):
|
|
7908
|
+
* the addonId of the provider the CALLER already resolved (e.g.
|
|
7909
|
+
* `buildCapCallFn`'s `deps.addonId` for a registered grouped-runner
|
|
7910
|
+
* provider). Only meaningful for `agent-child-forward` — a `remote-moleculer`
|
|
7911
|
+
* route already encodes the provider in its action name
|
|
7912
|
+
* (`${addonId}.${cap}.${method}`, via `NodeCapAuthority.getAddonId`), and a
|
|
7913
|
+
* `hub-local-uds` caller that already knows the addonId (`buildCapCallFn`)
|
|
7914
|
+
* talks to `LocalChildRegistry.callCapOnChild` directly rather than through
|
|
7915
|
+
* `dispatch`.
|
|
7916
|
+
* @param callerAddonId Optional out-of-band CALLER-identity hint — the
|
|
7917
|
+
* addon that ORIGINATED the call, distinct from `addonId` above (which
|
|
7918
|
+
* names the resolved PROVIDER, not the caller). Set by
|
|
7919
|
+
* `onUnownedCall` from `CapCallInput.callerAddonId`
|
|
7920
|
+
* (`LocalChildRegistry` stamped it off the registered `childId`). Only
|
|
7921
|
+
* the `hub-in-process` branch consumes it — merged into `args` so a
|
|
7922
|
+
* diagnostic tool deep in that provider (e.g. device-manager's
|
|
7923
|
+
* fleet-read census) can read it without every provider method changing
|
|
7924
|
+
* signature, the same "extra field the provider destructures or
|
|
7925
|
+
* ignores" convention `nodeId` already uses on this path.
|
|
7461
7926
|
*/
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7927
|
+
async dispatch(route, method, args, addonId, callerAddonId) {
|
|
7928
|
+
try {
|
|
7929
|
+
return await this.dispatchInner(route, method, args, addonId, callerAddonId);
|
|
7930
|
+
} catch (err) {
|
|
7931
|
+
if (err instanceof CapRouteError) throw err;
|
|
7932
|
+
const nodeId = this.routeNodeId(route);
|
|
7933
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
7934
|
+
throw new CapRouteError(route.capName, method, {
|
|
7935
|
+
reason: "transport-failed",
|
|
7936
|
+
nodeId,
|
|
7937
|
+
rejected: [{
|
|
7938
|
+
kind: route.kind,
|
|
7939
|
+
why: cause.message
|
|
7940
|
+
}]
|
|
7941
|
+
}, cause);
|
|
7942
|
+
}
|
|
7472
7943
|
}
|
|
7473
7944
|
/**
|
|
7474
|
-
*
|
|
7475
|
-
*
|
|
7476
|
-
* Requires `start()` to have been called; throws with a clear message if not.
|
|
7945
|
+
* Inner dispatch: may throw CapRouteError (validation failures) or arbitrary
|
|
7946
|
+
* transport errors. The outer `dispatch` wraps non-CapRouteErrors.
|
|
7477
7947
|
*/
|
|
7478
|
-
async
|
|
7479
|
-
|
|
7480
|
-
|
|
7948
|
+
async dispatchInner(route, method, args, addonId, callerAddonId) {
|
|
7949
|
+
switch (route.kind) {
|
|
7950
|
+
case "hub-in-process": return route.ref.invoke(method, withCallerAddonId(args, callerAddonId));
|
|
7951
|
+
case "hub-local-uds": {
|
|
7952
|
+
const registry = this.hubLocalRegistry;
|
|
7953
|
+
if (registry === null) throw new CapRouteError(route.capName, method, {
|
|
7954
|
+
reason: "no-provider",
|
|
7955
|
+
rejected: [{
|
|
7956
|
+
kind: "hub-local-uds",
|
|
7957
|
+
why: "UDS registry not available"
|
|
7958
|
+
}]
|
|
7959
|
+
});
|
|
7960
|
+
const deviceId = extractDeviceId(args);
|
|
7961
|
+
const input = {
|
|
7962
|
+
capName: route.capName,
|
|
7963
|
+
method,
|
|
7964
|
+
args,
|
|
7965
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
7966
|
+
};
|
|
7967
|
+
return registry.callCapOnChild(route.childId, input);
|
|
7968
|
+
}
|
|
7969
|
+
case "remote-moleculer": {
|
|
7970
|
+
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
7971
|
+
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
7972
|
+
reason: "no-provider",
|
|
7973
|
+
nodeId: route.nodeId,
|
|
7974
|
+
rejected: [{
|
|
7975
|
+
kind: "remote-moleculer",
|
|
7976
|
+
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
7977
|
+
}]
|
|
7978
|
+
});
|
|
7979
|
+
const deviceId = extractDeviceId(args);
|
|
7980
|
+
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
7981
|
+
const action = capActionName(addonId, route.capName, method, isNative);
|
|
7982
|
+
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
7983
|
+
nodeID: route.nodeId,
|
|
7984
|
+
timeout: this.callTimeout(route.capName, method)
|
|
7985
|
+
});
|
|
7986
|
+
}
|
|
7987
|
+
case "agent-child-forward": {
|
|
7988
|
+
const deviceId = extractDeviceId(args);
|
|
7989
|
+
const params = {
|
|
7990
|
+
capName: route.capName,
|
|
7991
|
+
method,
|
|
7992
|
+
args,
|
|
7993
|
+
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
7994
|
+
...deviceId !== void 0 ? { deviceId } : {},
|
|
7995
|
+
...addonId !== void 0 ? { addonId } : {}
|
|
7996
|
+
};
|
|
7997
|
+
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
7998
|
+
nodeID: route.agentNodeId,
|
|
7999
|
+
timeout: this.callTimeout(route.capName, method)
|
|
8000
|
+
});
|
|
8001
|
+
}
|
|
8002
|
+
}
|
|
7481
8003
|
}
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
8004
|
+
buildSnapshot() {
|
|
8005
|
+
const hubLocalRegistry = this.hubLocalRegistry;
|
|
8006
|
+
const nodeAuthority = this.nodeAuthority;
|
|
8007
|
+
const inProcessProviders = this.inProcessProviders;
|
|
8008
|
+
const hubResidentProviders = this.hubResidentProviders;
|
|
8009
|
+
return {
|
|
8010
|
+
hubNodeId: this.hubNodeId,
|
|
8011
|
+
hubInProcessProvides: (cap) => inProcessProviders(cap) !== null,
|
|
8012
|
+
...hubResidentProviders !== void 0 ? {
|
|
8013
|
+
hubResidentProvides: (cap) => hubResidentProviders(cap) !== null,
|
|
8014
|
+
getHubResidentProviderRef: (cap) => hubResidentProviders(cap)
|
|
8015
|
+
} : {},
|
|
8016
|
+
hubLocalChildProvides: (cap, deviceId) => hubLocalRegistry !== null && hubLocalRegistry.resolveChildId(cap, deviceId) !== null,
|
|
8017
|
+
nodeKnowsCap: (nodeId, cap) => nodeAuthority.nodeKnowsCap(nodeId, cap),
|
|
8018
|
+
nodeIsAgent: (nodeId) => nodeAuthority.nodeIsAgent(nodeId),
|
|
8019
|
+
nodeOnline: (nodeId) => nodeAuthority.nodeOnline(nodeId),
|
|
8020
|
+
listKnownNodeIds: () => nodeAuthority.listNodeIds(),
|
|
8021
|
+
getInProcessProviderRef: (cap) => inProcessProviders(cap),
|
|
8022
|
+
getHubLocalChildId: (cap, deviceId) => hubLocalRegistry !== null ? hubLocalRegistry.resolveChildId(cap, deviceId) : null,
|
|
8023
|
+
getAgentChildId: (agentNodeId, cap) => nodeAuthority.getAgentChildId(agentNodeId, cap)
|
|
7498
8024
|
};
|
|
7499
|
-
return this.channel.request(msg);
|
|
7500
8025
|
}
|
|
7501
|
-
/**
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
8026
|
+
/** Extract a nodeId string from a route for error reporting, or undefined. */
|
|
8027
|
+
routeNodeId(route) {
|
|
8028
|
+
switch (route.kind) {
|
|
8029
|
+
case "hub-in-process": return this.hubNodeId;
|
|
8030
|
+
case "hub-local-uds": return `${this.hubNodeId}/${route.childId}`;
|
|
8031
|
+
case "remote-moleculer": return route.nodeId;
|
|
8032
|
+
case "agent-child-forward": return route.agentNodeId;
|
|
8033
|
+
}
|
|
7506
8034
|
}
|
|
7507
8035
|
};
|
|
7508
8036
|
//#endregion
|
|
@@ -10174,4 +10702,4 @@ async function defaultRun(command, args) {
|
|
|
10174
10702
|
await execFileAsync(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
|
|
10175
10703
|
}
|
|
10176
10704
|
//#endregion
|
|
10177
|
-
export { buildUdsNativeCapProxy as $, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO as $t, createUdsLoggerWithControl as A, HUB_MAIN_RSS_BUDGET_MB as At, readMoleculerFanoutMode as B, createV8Reclaimer as Bt, ipcParentLink as C,
|
|
10705
|
+
export { buildUdsNativeCapProxy as $, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO as $t, createUdsLoggerWithControl as A, sampleSocketDirection as An, HUB_MAIN_RSS_BUDGET_MB as At, readMoleculerFanoutMode as B, createV8Reclaimer as Bt, ipcParentLink as C, createSocketPlaneReader as Cn, createAddonDataPlaneFacility as Ct, createUdsEventBus as D, EMPTY_SOCKET_DIRECTION as Dn, HEAP_WATCH_INTERVAL_MS as Dt, createUdsEventBridge as E, readSocketQueues as En, HEAP_RECLAIM_TRIGGER_MB as Et, CapRouteError as F, createEventPlaneReader as Fn, RUNNER_RSS_BUDGET_ENV as Ft, createLocalTransport as G, reclaimIntervalMs as Gt, PARENT_ROUTED_PROVIDER_ADDON_ID as H, hubMainRssBudget as Ht, classifyCapRoute as I, diffEventPlane as In, ZERO_LOOP_DELAY as It, SocketChannel as J, startRunnerHeapWatch as Jt, UdsLocalTransportClient as K, shouldReclaim as Kt, LocalChildClient as L, formatEventPlane as Ln, buildHeapSample as Lt, AGENT_CAP_FWD_SERVICE as M, socketDirectionMessages as Mn, RSS_BUDGET_REANNOUNCE_MIN_MS as Mt, CapRouteResolver as N, EVENT_PLANE_TOP_N as Nn, RSS_BUDGET_RELEASE_RATIO as Nt, udsChildLogToWorkerEntry as O, createSocketDirectionCounters as On, HEAP_WATCH_WARN_RATIO as Ot, callWithServiceDiscovery as P, createEventPlaneMeter as Pn, RUNNER_HEAP_WATCH_INTERVAL_MS as Pt, buildNativeCapProxy as Q, ARRAY_BUFFER_CENSUS_THRESHOLD_MB as Qt, LocalChildRegistry as R, createDeferredHeapWatchSink as Rt, buildLinkChain as S, createSocketPlaneMeter as Sn, runNpm as St, createParentUnownedCallHandler as T, formatSocketPlane as Tn, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as Tt, __resetCapUsageRegistryForTests as U, nextRssBudgetState as Ut, CapUsageRegistry as V, describeRss as Vt, getCapUsageRegistry as W, parseRssBudgetMb as Wt, FrameDecoder as X, ARRAY_BUFFER_CENSUS_COOLDOWN_MS as Xt, localEndpointPath as Y, strandedMb as Yt, encodeFrame as Z, ARRAY_BUFFER_CENSUS_SUSTAIN_MS as Zt, AddonDepsManager as _, createMeshQueueReader as _n, CapabilityUnavailableError as _t, createUdsAddonContext as a, HEAP_SPACE_REPORT_MIN_MB as an, createAddonService as at, brokerCallForCap as b, SOCKET_PLANE_TOP_N as bn, resolveAddonClass as bt, getOrInitReadinessRegistryForClient as c, heapSpaceField as cn, capActionName as ct, clusterEventTopic as d, OUTBOUND_QUEUE_COOLDOWN_MS as dn, capServiceName as dt, createArrayBufferCensusGate as en, createBrokerDeviceManagerApi as et, getBrokerEventBus as f, OUTBOUND_QUEUE_SUSTAIN_MS as fn, parseCapAction as ft, subscribePassthrough as g, MESH_QUEUE_TOP_N as gn, CapabilityHandle as gt, setNodeEventInterest as h, formatOutboundQueueVerdict as hn, DeviceRegistry as ht, createAddonContext as i, DECISIVE_HEAP_SPACES as in, setWorkerNativeCapsChangeListener as it, AGENT_CAP_FWD_ACTION as j, socketDirectionBytes as jn, HUB_RSS_BUDGET_ENV as jt, createUdsLogger as k, recordSocketFrame as kn, HUB_MAIN_HEAP_WATCH_LABEL as kt, setHubConnected as l, readHeapSpaces as ln, capActionSuffix as lt, registerEventBusService as m, createOutboundQueueGate as mn, serializeTypedArrays as mt, installManifestPythonDeps as n, formatArrayBufferCensus as nn, getWorkerNativeCapSnapshot as nt, getWorkerDeviceRegistry as o, emitHeapDiagnosticReport as on, validateProviderRegistrations as ot, getMoleculerEventStats as p, OUTBOUND_QUEUE_THRESHOLD_BYTES as pn, deserializeTypedArrays as pt, UdsLocalTransportServer as q, startHeapWatch as qt, adaptBrokerToCluster as r, isArrayBufferCensusReport as rn, mountNativeCapService as rt, getOrInitReadinessRegistry as s, formatHeapSpaces as sn, NATIVE_PROVIDER_SERVICE_INFIX as st, installManifestSystemDeps as t, createInspectorArrayBufferCensus as tn, getWorkerNativeCapProvider as tt, EVENT_TOPIC_PREFIX as u, selectReportedSpaces as un, capBareAction as ut, createKernelHwAccel as v, formatMeshQueue as vn, copyBundledNativeModules as vt, localProviderLink as w, diffSocketPlane as wn, HEAP_RECLAIM_MIN_INTERVAL_MS as wt, brokerTransportLink as x, SOCKET_QUEUE_TOP_N as xn, resolveNpmInvocation as xt, resolveHwAccel as y, EMPTY_SOCKET_PLANE_BASELINE as yn, installManifestNativeDeps as yt, UDS_NO_ROUTE_PREFIX as z, createLoopDelayMeter as zt };
|