@camstack/system 1.2.288 → 1.2.290

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/camera-grid/index.js +1 -1
  14. package/dist/builtins/camera-grid/index.mjs +1 -1
  15. package/dist/builtins/console-logging/index.js +1 -1
  16. package/dist/builtins/console-logging/index.mjs +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  18. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  19. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  23. package/dist/builtins/hub-forwarder/index.js +1 -1
  24. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  29. package/dist/builtins/local-network/local-network.addon.js +1 -1
  30. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  31. package/dist/builtins/loki-logging/index.js +1 -1
  32. package/dist/builtins/loki-logging/index.mjs +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  35. package/dist/builtins/platform-probe/index.js +1 -1
  36. package/dist/builtins/platform-probe/index.mjs +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  39. package/dist/builtins/snapshot/index.js +1 -1
  40. package/dist/builtins/snapshot/index.mjs +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  44. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  47. package/dist/builtins/system-config/system-config.addon.js +1 -1
  48. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  49. package/dist/builtins/winston-logging/index.js +1 -1
  50. package/dist/builtins/winston-logging/index.mjs +1 -1
  51. package/dist/{dist-DKFb2936.mjs → dist-BkjMEa2F.mjs} +230 -10
  52. package/dist/{dist-BYnHJU96.js → dist-KRK9zLWt.js} +247 -9
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +5 -3
  55. package/dist/index.mjs +4 -4
  56. package/dist/kernel/moleculer/addon-peer-bytes-facility.d.ts +24 -0
  57. package/dist/{manifest-system-deps-C9T7784z.mjs → manifest-system-deps-D_5UXqDb.mjs} +585 -5
  58. package/dist/{manifest-system-deps-B8ZUclN4.js → manifest-system-deps-Dm7QhyvL.js} +592 -0
  59. package/dist/{retired-settings-keys-C6rFqHR7.js → retired-settings-keys-BSLMlTcz.js} +1 -1
  60. package/dist/{retired-settings-keys-tuNxNNpR.mjs → retired-settings-keys-DNK-p6aa.mjs} +1 -1
  61. package/package.json +1 -1
@@ -1,22 +1,24 @@
1
+ import { C as PEER_BYTES_CHUNK_BYTES } from "./dist-BkjMEa2F.mjs";
1
2
  import "./hub-cap-forward-BdT320dq.mjs";
2
3
  import * as crypto$1 from "node:crypto";
3
4
  import { randomBytes, randomUUID } from "node:crypto";
4
5
  import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
5
- import { unlink } from "node:fs/promises";
6
+ import { mkdir, rm, stat, unlink } from "node:fs/promises";
6
7
  import * as path$1 from "node:path";
7
- import { isAbsolute, join } from "node:path";
8
+ import { dirname, isAbsolute, join } from "node:path";
8
9
  import * as fs from "node:fs";
9
- import { existsSync, readFileSync, readdirSync } from "node:fs";
10
+ import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync } from "node:fs";
10
11
  import { execFile } from "node:child_process";
11
12
  import * as os from "node:os";
12
13
  import { tmpdir } from "node:os";
13
14
  import { promisify } from "node:util";
14
- import { createServer } from "node:http";
15
+ import { createServer, request } from "node:http";
15
16
  import * as v8 from "node:v8";
16
17
  import { Session } from "node:inspector/promises";
17
18
  import { monitorEventLoopDelay } from "node:perf_hooks";
18
19
  import * as vm from "node:vm";
19
20
  import { BOOT_RECOVERY_BACKOFF_MS, DATAPLANE_SECRET_HEADER, DEVICE_CHILDREN_BATCH_MAX, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, createEventBusSliceSource, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
21
+ import { once } from "node:events";
20
22
  import { connect, createConnection, createServer as createServer$1 } from "node:net";
21
23
  import { TRPCClientError, createTRPCClient } from "@trpc/client";
22
24
  //#region src/kernel/event-plane-report.ts
@@ -2610,6 +2612,577 @@ function createAddonDataPlaneFacility(args) {
2610
2612
  };
2611
2613
  }
2612
2614
  //#endregion
2615
+ //#region src/kernel/moleculer/addon-peer-bytes-facility.ts
2616
+ /**
2617
+ * Kernel side of the addon→addon **byte transport** (`ctx.peerBytes`, D613).
2618
+ *
2619
+ * See {@link AddonPeerBytes} for what this exists for and what it deliberately
2620
+ * does not do. This file is the mechanism:
2621
+ *
2622
+ * - **One lazy loopback listener per addon process.** Bound on the first
2623
+ * `offer()`, never before, so an addon that never offers pays no port.
2624
+ * - **One token per offer.** 24 random bytes, base64url, ONE `GET`. A replay
2625
+ * is `offer-used` (410) for as long as the original ticket would have lived,
2626
+ * so a second take is told what happened rather than "no such thing".
2627
+ * `HEAD` answers the headers and consumes nothing.
2628
+ * - **Outstanding offers are bounded and swept on every mint**, which is also
2629
+ * what makes a per-frame misuse of this facility refuse by name instead of
2630
+ * working slowly.
2631
+ * - **Every write honours `write()`'s return and awaits `drain`** (D447), and
2632
+ * a peer that has taken nothing for {@link PEER_BYTES_STALL_MS} is cut. Every
2633
+ * exit — offered, taken, sent, cut, stalled, refused, disposed — is one log
2634
+ * line, carrying `tags: { deviceId }` when the offer named a device.
2635
+ * - **The consumer's response is handed over PAUSED**, which is the whole
2636
+ * reason `open()` lives in the framework rather than in each consumer: see
2637
+ * {@link PeerBytesBody}.
2638
+ */
2639
+ /** The HOST a node id belongs to. `hub` → `hub`; `hub/runner-3` → `hub`. */
2640
+ function hostNodeIdOf(nodeId) {
2641
+ const slash = nodeId.indexOf("/");
2642
+ return slash === -1 ? nodeId : nodeId.slice(0, slash);
2643
+ }
2644
+ /** A body chunk, without a cast. A readable in binary mode yields Buffers; in
2645
+ * string mode, strings. Anything else is a broken stream and says so. */
2646
+ function asBuffer(chunk) {
2647
+ if (Buffer.isBuffer(chunk)) return chunk;
2648
+ if (typeof chunk === "string") return Buffer.from(chunk);
2649
+ throw new Error("peer-bytes: a body chunk was neither a Buffer nor a string");
2650
+ }
2651
+ function tokenOf(req) {
2652
+ const raw = req.url ?? "/";
2653
+ const query = raw.indexOf("?");
2654
+ return (query === -1 ? raw : raw.slice(0, query)).replace(/^\/+/, "");
2655
+ }
2656
+ /**
2657
+ * Write `bytes` to `res` in bounded chunks, honouring backpressure.
2658
+ *
2659
+ * D447, the consuming half stated from the producing side: a raw HTTP stream
2660
+ * writes no chunk the socket has not taken. `write()` returning false means the
2661
+ * kernel buffer is full and everything further is heap in THIS process — so we
2662
+ * wait for `drain`, and a peer that never drains within `stallMs` is cut rather
2663
+ * than held. Returns how many bytes reached the socket and why it stopped.
2664
+ */
2665
+ async function writeBufferWithBackpressure(res, bytes, stallMs) {
2666
+ const waitForDrain = () => new Promise((resolve) => {
2667
+ const settle = (outcome) => {
2668
+ clearTimeout(timer);
2669
+ res.off("drain", onDrain);
2670
+ res.off("close", onClose);
2671
+ resolve(outcome);
2672
+ };
2673
+ const onDrain = () => {
2674
+ settle("drained");
2675
+ };
2676
+ const onClose = () => {
2677
+ settle("closed");
2678
+ };
2679
+ const timer = setTimeout(() => {
2680
+ settle("stalled");
2681
+ }, stallMs);
2682
+ timer.unref();
2683
+ res.on("drain", onDrain);
2684
+ res.on("close", onClose);
2685
+ });
2686
+ let sent = 0;
2687
+ while (sent < bytes.byteLength) {
2688
+ if (res.destroyed || res.writableEnded) return {
2689
+ sent,
2690
+ outcome: "peer-gone"
2691
+ };
2692
+ const end = Math.min(sent + PEER_BYTES_CHUNK_BYTES, bytes.byteLength);
2693
+ const took = res.write(bytes.subarray(sent, end));
2694
+ sent = end;
2695
+ if (took) continue;
2696
+ const waited = await waitForDrain();
2697
+ if (waited === "drained") continue;
2698
+ return {
2699
+ sent,
2700
+ outcome: waited === "closed" ? "peer-gone" : "stalled"
2701
+ };
2702
+ }
2703
+ return {
2704
+ sent,
2705
+ outcome: "sent"
2706
+ };
2707
+ }
2708
+ function createAddonPeerBytesFacility(args) {
2709
+ const now = args.now ?? (() => Date.now());
2710
+ const ttlMs = args.ttlMs ?? 3e4;
2711
+ const maxOutstanding = args.maxOutstanding ?? 64;
2712
+ const stallMs = args.stallMs ?? 15e3;
2713
+ const host = args.host ?? "127.0.0.1";
2714
+ const hostNodeId = hostNodeIdOf(args.nodeId);
2715
+ const offers = /* @__PURE__ */ new Map();
2716
+ /** Tokens already taken, kept for their TTL so a replay is `offer-used`. */
2717
+ const taken = /* @__PURE__ */ new Map();
2718
+ let server = null;
2719
+ let baseUrl = null;
2720
+ let listening = null;
2721
+ const sweep = () => {
2722
+ const at = now();
2723
+ for (const [token, offer] of offers) if (offer.expiresAtMs <= at) {
2724
+ offers.delete(token);
2725
+ args.logger.info("peer-bytes: an offer expired untaken", {
2726
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2727
+ meta: {
2728
+ label: offer.label,
2729
+ outstanding: offers.size
2730
+ }
2731
+ });
2732
+ }
2733
+ for (const [token, expiresAtMs] of taken) if (expiresAtMs <= at) taken.delete(token);
2734
+ };
2735
+ const route = (req, res) => {
2736
+ const token = tokenOf(req);
2737
+ const offer = offers.get(token);
2738
+ if (offer === void 0) {
2739
+ const status = taken.has(token) ? 410 : 404;
2740
+ res.writeHead(status, { "x-camstack-peer-bytes": status === 410 ? "offer-used" : "no-offer" });
2741
+ res.end();
2742
+ return;
2743
+ }
2744
+ if (offer.expiresAtMs <= now()) {
2745
+ offers.delete(token);
2746
+ res.writeHead(410, { "x-camstack-peer-bytes": "offer-expired" }).end();
2747
+ return;
2748
+ }
2749
+ if (req.method === "GET") {
2750
+ offers.delete(token);
2751
+ taken.set(token, offer.expiresAtMs);
2752
+ args.logger.info("peer-bytes: an offer was taken", {
2753
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2754
+ meta: {
2755
+ label: offer.label,
2756
+ outstanding: offers.size
2757
+ }
2758
+ });
2759
+ }
2760
+ Promise.resolve(offer.handler(req, res)).catch((err) => {
2761
+ args.logger.warn("peer-bytes: an offer handler threw", {
2762
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2763
+ meta: {
2764
+ label: offer.label,
2765
+ error: err instanceof Error ? err.message : String(err)
2766
+ }
2767
+ });
2768
+ if (!res.headersSent) res.writeHead(500);
2769
+ res.end();
2770
+ });
2771
+ };
2772
+ const listen = () => {
2773
+ if (listening !== null) return listening;
2774
+ listening = new Promise((resolve, reject) => {
2775
+ const created = createServer((req, res) => {
2776
+ route(req, res);
2777
+ });
2778
+ created.on("error", (err) => {
2779
+ listening = null;
2780
+ reject(err);
2781
+ });
2782
+ created.listen(0, host, () => {
2783
+ const address = created.address();
2784
+ if (address === null || typeof address === "string") {
2785
+ created.close();
2786
+ listening = null;
2787
+ reject(/* @__PURE__ */ new Error("the peer-bytes listener bound no TCP port"));
2788
+ return;
2789
+ }
2790
+ server = created;
2791
+ baseUrl = `http://${host}:${address.port}`;
2792
+ args.logger.info("peer-bytes: listener bound", { meta: {
2793
+ addonId: args.addonId,
2794
+ host,
2795
+ port: address.port,
2796
+ hostNodeId,
2797
+ ttlMs
2798
+ } });
2799
+ resolve(baseUrl);
2800
+ });
2801
+ });
2802
+ return listening;
2803
+ };
2804
+ const mint = async (options) => {
2805
+ sweep();
2806
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2807
+ if (offers.size >= maxOutstanding) {
2808
+ args.logger.warn("peer-bytes: offer refused — too many outstanding", {
2809
+ ...deviceTags,
2810
+ meta: {
2811
+ label: options.label,
2812
+ outstanding: offers.size,
2813
+ maxOutstanding
2814
+ }
2815
+ });
2816
+ return {
2817
+ kind: "refused",
2818
+ code: "offer-exhausted",
2819
+ detail: `${String(offers.size)} offers outstanding against a bound of ${String(maxOutstanding)}`
2820
+ };
2821
+ }
2822
+ let base;
2823
+ try {
2824
+ base = baseUrl ?? await listen();
2825
+ } catch (err) {
2826
+ const detail = err instanceof Error ? err.message : String(err);
2827
+ args.logger.warn("peer-bytes: offer refused — no listener", {
2828
+ ...deviceTags,
2829
+ meta: {
2830
+ label: options.label,
2831
+ error: detail
2832
+ }
2833
+ });
2834
+ return {
2835
+ kind: "refused",
2836
+ code: "listener-failed",
2837
+ detail
2838
+ };
2839
+ }
2840
+ const token = randomBytes(24).toString("base64url");
2841
+ const expiresAtMs = now() + (options.ttlMs ?? ttlMs);
2842
+ offers.set(token, {
2843
+ handler: options.handler,
2844
+ label: options.label,
2845
+ deviceId: options.deviceId,
2846
+ expiresAtMs
2847
+ });
2848
+ args.logger.info("peer-bytes: offer minted", {
2849
+ ...deviceTags,
2850
+ meta: {
2851
+ label: options.label,
2852
+ declaredBytes: options.declaredBytes ?? null,
2853
+ expiresInMs: expiresAtMs - now(),
2854
+ outstanding: offers.size
2855
+ }
2856
+ });
2857
+ return {
2858
+ kind: "ticket",
2859
+ ticket: {
2860
+ url: `${base}/${token}`,
2861
+ hostNodeId,
2862
+ expiresAtMs,
2863
+ declaredBytes: options.declaredBytes ?? null
2864
+ }
2865
+ };
2866
+ };
2867
+ const peerBytes = {
2868
+ hostNodeId,
2869
+ get outstanding() {
2870
+ sweep();
2871
+ return offers.size;
2872
+ },
2873
+ offer: mint,
2874
+ offerBuffer: async (options) => {
2875
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2876
+ const handler = async (req, res) => {
2877
+ res.writeHead(200, {
2878
+ "content-type": options.contentType,
2879
+ "content-length": String(options.bytes.byteLength),
2880
+ "accept-ranges": "none"
2881
+ });
2882
+ if (req.method === "HEAD") {
2883
+ res.end();
2884
+ return;
2885
+ }
2886
+ const startedAt = now();
2887
+ const result = await writeBufferWithBackpressure(res, options.bytes, stallMs);
2888
+ if (result.outcome === "sent") {
2889
+ res.end();
2890
+ args.logger.info("peer-bytes: body sent", {
2891
+ ...deviceTags,
2892
+ meta: {
2893
+ label: options.label,
2894
+ bytes: result.sent,
2895
+ tookMs: now() - startedAt,
2896
+ source: "buffer"
2897
+ }
2898
+ });
2899
+ return;
2900
+ }
2901
+ args.logger.warn("peer-bytes: body cut before it was complete", {
2902
+ ...deviceTags,
2903
+ meta: {
2904
+ label: options.label,
2905
+ branch: result.outcome,
2906
+ sent: result.sent,
2907
+ of: options.bytes.byteLength,
2908
+ stallMs,
2909
+ source: "buffer"
2910
+ }
2911
+ });
2912
+ res.destroy();
2913
+ };
2914
+ return await mint({
2915
+ handler,
2916
+ label: options.label,
2917
+ declaredBytes: options.bytes.byteLength,
2918
+ ...options.deviceId !== void 0 ? { deviceId: options.deviceId } : {},
2919
+ ...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {}
2920
+ });
2921
+ },
2922
+ offerFile: async (options) => {
2923
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2924
+ let declaredBytes = null;
2925
+ try {
2926
+ declaredBytes = (await stat(options.path)).size;
2927
+ } catch {
2928
+ declaredBytes = null;
2929
+ }
2930
+ const handler = async (req, res) => {
2931
+ const headers = {
2932
+ "content-type": options.contentType,
2933
+ "accept-ranges": "none"
2934
+ };
2935
+ if (declaredBytes !== null) headers["content-length"] = String(declaredBytes);
2936
+ if (req.method === "HEAD") {
2937
+ res.writeHead(200, headers).end();
2938
+ return;
2939
+ }
2940
+ const startedAt = now();
2941
+ let stream;
2942
+ try {
2943
+ stream = createReadStream(options.path);
2944
+ await once(stream, "open");
2945
+ } catch (err) {
2946
+ const detail = err instanceof Error ? err.message : String(err);
2947
+ args.logger.warn("peer-bytes: the offered file could not be opened", {
2948
+ ...deviceTags,
2949
+ meta: {
2950
+ label: options.label,
2951
+ error: detail
2952
+ }
2953
+ });
2954
+ if (!res.headersSent) res.writeHead(503, { "x-camstack-peer-bytes": "file-unreadable" });
2955
+ res.end();
2956
+ return;
2957
+ }
2958
+ res.writeHead(200, headers);
2959
+ let sent = 0;
2960
+ let lastProgressAt = now();
2961
+ stream.pipe(res);
2962
+ stream.on("data", (chunk) => {
2963
+ sent += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
2964
+ lastProgressAt = now();
2965
+ });
2966
+ const stallTimer = setInterval(() => {
2967
+ if (now() - lastProgressAt < stallMs) return;
2968
+ clearInterval(stallTimer);
2969
+ args.logger.warn("peer-bytes: body cut before it was complete", {
2970
+ ...deviceTags,
2971
+ meta: {
2972
+ label: options.label,
2973
+ branch: "stalled",
2974
+ sent,
2975
+ stallMs,
2976
+ source: "file"
2977
+ }
2978
+ });
2979
+ stream.destroy();
2980
+ res.destroy();
2981
+ }, Math.max(1e3, Math.floor(stallMs / 4)));
2982
+ stallTimer.unref();
2983
+ const ended = await Promise.race([once(stream, "end").then(() => true), once(res, "close").then(() => false)]).catch(() => false);
2984
+ clearInterval(stallTimer);
2985
+ if (ended) {
2986
+ args.logger.info("peer-bytes: body sent", {
2987
+ ...deviceTags,
2988
+ meta: {
2989
+ label: options.label,
2990
+ bytes: sent,
2991
+ tookMs: now() - startedAt,
2992
+ source: "file"
2993
+ }
2994
+ });
2995
+ return;
2996
+ }
2997
+ stream.destroy();
2998
+ args.logger.warn("peer-bytes: body cut before it was complete", {
2999
+ ...deviceTags,
3000
+ meta: {
3001
+ label: options.label,
3002
+ branch: "peer-gone",
3003
+ sent,
3004
+ of: declaredBytes,
3005
+ source: "file"
3006
+ }
3007
+ });
3008
+ };
3009
+ return await mint({
3010
+ handler,
3011
+ label: options.label,
3012
+ ...declaredBytes !== null ? { declaredBytes } : {},
3013
+ ...options.deviceId !== void 0 ? { deviceId: options.deviceId } : {},
3014
+ ...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {}
3015
+ });
3016
+ },
3017
+ open: async (ticket) => {
3018
+ if (ticket.hostNodeId !== hostNodeId) {
3019
+ args.logger.warn("peer-bytes: refusing a ticket minted on another node", { meta: {
3020
+ mintedOn: ticket.hostNodeId,
3021
+ here: hostNodeId
3022
+ } });
3023
+ return {
3024
+ kind: "refused",
3025
+ code: "cross-node",
3026
+ detail: `this ticket was minted on node "${ticket.hostNodeId}" and this process runs on "${hostNodeId}" — peer-bytes is loopback only, so there is nothing to dial`
3027
+ };
3028
+ }
3029
+ if (ticket.expiresAtMs <= now()) return {
3030
+ kind: "refused",
3031
+ code: "ticket-expired",
3032
+ detail: `this ticket expired ${String(now() - ticket.expiresAtMs)} ms ago`
3033
+ };
3034
+ let url;
3035
+ try {
3036
+ url = new URL(ticket.url);
3037
+ } catch {
3038
+ return {
3039
+ kind: "refused",
3040
+ code: "ticket-malformed",
3041
+ detail: `"${ticket.url}" is not a URL`
3042
+ };
3043
+ }
3044
+ if (url.protocol !== "http:") return {
3045
+ kind: "refused",
3046
+ code: "ticket-malformed",
3047
+ detail: `peer-bytes dials http, not "${url.protocol}"`
3048
+ };
3049
+ const req = request({
3050
+ hostname: url.hostname,
3051
+ port: url.port,
3052
+ path: url.pathname,
3053
+ method: "GET"
3054
+ });
3055
+ let response;
3056
+ try {
3057
+ response = await new Promise((resolve, reject) => {
3058
+ req.once("response", resolve);
3059
+ req.once("error", reject);
3060
+ req.end();
3061
+ });
3062
+ } catch (err) {
3063
+ const detail = err instanceof Error ? err.message : String(err);
3064
+ req.destroy();
3065
+ args.logger.warn("peer-bytes: the dial failed", { meta: { error: detail } });
3066
+ return {
3067
+ kind: "refused",
3068
+ code: "unreachable",
3069
+ detail
3070
+ };
3071
+ }
3072
+ response.pause();
3073
+ const status = response.statusCode ?? 0;
3074
+ if (status < 200 || status >= 300) {
3075
+ const reason = response.headers["x-camstack-peer-bytes"];
3076
+ response.resume();
3077
+ req.destroy();
3078
+ return {
3079
+ kind: "refused",
3080
+ code: "producer-refused",
3081
+ detail: typeof reason === "string" ? `the producer answered ${String(status)} (${reason})` : `the producer answered ${String(status)}`,
3082
+ status
3083
+ };
3084
+ }
3085
+ const lengthHeader = response.headers["content-length"];
3086
+ const declaredBytes = typeof lengthHeader === "string" && /^\d+$/.test(lengthHeader) ? Number(lengthHeader) : null;
3087
+ const contentTypeHeader = response.headers["content-type"];
3088
+ const stream = response;
3089
+ return {
3090
+ kind: "body",
3091
+ body: {
3092
+ status,
3093
+ contentType: typeof contentTypeHeader === "string" ? contentTypeHeader : null,
3094
+ declaredBytes,
3095
+ stream,
3096
+ cancel: (reason) => {
3097
+ args.logger.info("peer-bytes: the consumer gave up on a body", { meta: { reason } });
3098
+ response.destroy();
3099
+ req.destroy();
3100
+ },
3101
+ writeToFile: async (path, options) => {
3102
+ await mkdir(dirname(path), { recursive: true });
3103
+ const sink = createWriteStream(path);
3104
+ const discard = async () => {
3105
+ sink.destroy();
3106
+ if (!sink.closed) await once(sink, "close").catch(() => void 0);
3107
+ await rm(path, { force: true });
3108
+ };
3109
+ let written = 0;
3110
+ let overrun = false;
3111
+ try {
3112
+ for await (const chunk of stream) {
3113
+ const buf = asBuffer(chunk);
3114
+ written += buf.byteLength;
3115
+ if (written > options.maxBytes) {
3116
+ overrun = true;
3117
+ break;
3118
+ }
3119
+ if (!sink.write(buf)) await once(sink, "drain");
3120
+ }
3121
+ } catch (err) {
3122
+ const detail = err instanceof Error ? err.message : String(err);
3123
+ response.destroy();
3124
+ req.destroy();
3125
+ await discard();
3126
+ return {
3127
+ kind: "refused",
3128
+ code: "transfer-failed",
3129
+ detail,
3130
+ bytes: written
3131
+ };
3132
+ }
3133
+ if (overrun) {
3134
+ response.destroy();
3135
+ req.destroy();
3136
+ await discard();
3137
+ return {
3138
+ kind: "refused",
3139
+ code: "too-large",
3140
+ detail: `the body passed ${String(options.maxBytes)} bytes and was aborted`,
3141
+ bytes: written
3142
+ };
3143
+ }
3144
+ try {
3145
+ sink.end();
3146
+ await once(sink, "close");
3147
+ } catch (err) {
3148
+ const detail = err instanceof Error ? err.message : String(err);
3149
+ await discard();
3150
+ return {
3151
+ kind: "refused",
3152
+ code: "write-failed",
3153
+ detail,
3154
+ bytes: written
3155
+ };
3156
+ }
3157
+ return {
3158
+ kind: "written",
3159
+ bytes: written
3160
+ };
3161
+ }
3162
+ }
3163
+ };
3164
+ }
3165
+ };
3166
+ const dispose = async () => {
3167
+ offers.clear();
3168
+ taken.clear();
3169
+ const open = server;
3170
+ server = null;
3171
+ baseUrl = null;
3172
+ listening = null;
3173
+ if (open === null) return;
3174
+ await new Promise((resolve) => {
3175
+ open.close(() => resolve());
3176
+ open.closeAllConnections();
3177
+ });
3178
+ args.logger.info("peer-bytes: listener closed", { meta: { addonId: args.addonId } });
3179
+ };
3180
+ return {
3181
+ peerBytes,
3182
+ dispose
3183
+ };
3184
+ }
3185
+ //#endregion
2613
3186
  //#region src/kernel/deps/npm-command.ts
2614
3187
  /**
2615
3188
  * npm invocation resolver — makes "is npm available?" a solved problem for
@@ -11187,6 +11760,12 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
11187
11760
  ...options?.dataPlaneSink !== void 0 ? { sink: options.dataPlaneSink } : {}
11188
11761
  });
11189
11762
  workerDisposerChain.add(() => dataPlaneFacility.dispose());
11763
+ const peerBytesFacility = createAddonPeerBytesFacility({
11764
+ addonId,
11765
+ logger: scopedLogger,
11766
+ nodeId
11767
+ });
11768
+ workerDisposerChain.add(() => peerBytesFacility.dispose());
11190
11769
  return {
11191
11770
  id: addonId,
11192
11771
  logger: scopedLogger,
@@ -11195,6 +11774,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
11195
11774
  dataDir: addonDataDir,
11196
11775
  nodeDataDir,
11197
11776
  dataPlane: dataPlaneFacility.dataPlane,
11777
+ peerBytes: peerBytesFacility.peerBytes,
11198
11778
  api,
11199
11779
  deps: new AddonDepsManager(addonDataDir, scopedLogger),
11200
11780
  settings: settingsView,
@@ -11691,4 +12271,4 @@ async function defaultRun(command, args) {
11691
12271
  await execFileAsync(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
11692
12272
  }
11693
12273
  //#endregion
11694
- export { buildNativeCapProxy as $, ARRAY_BUFFER_CENSUS_SUSTAIN_MS as $t, createUdsLogger as A, createSocketDirectionCounters as An, HEAP_WATCH_WARN_RATIO as At, UDS_NO_ROUTE_PREFIX as B, createCapActionCensus as Bn, createDeferredHeapWatchSink as Bt, deviceCapResolverLink as C, SOCKET_QUEUE_TOP_N as Cn, resolveNpmInvocation as Ct, createUdsEventBridge as D, formatSocketPlane as Dn, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as Dt, createParentUnownedCallHandler as E, diffSocketPlane as En, HEAP_RECLAIM_MIN_INTERVAL_MS as Et, callWithServiceDiscovery as F, CAP_ACTION_OVERFLOW_LABEL as Fn, RSS_BUDGET_RELEASE_RATIO as Ft, getCapUsageRegistry as G, diffEventPlane as Gn, nextRssBudgetState as Gt, CapUsageRegistry as H, EVENT_PLANE_TOP_N as Hn, createV8Reclaimer as Ht, CapRouteError as I, CAP_ACTION_UNLABELLED as In, RUNNER_HEAP_WATCH_INTERVAL_MS as It, UdsLocalTransportServer as J, shouldReclaim as Jt, createLocalTransport as K, formatEventPlane as Kn, parseRssBudgetMb as Kt, classifyCapRoute as L, EMPTY_CAP_ACTION_READING as Ln, RUNNER_RSS_BUDGET_ENV as Lt, AGENT_CAP_FWD_ACTION as M, sampleSocketDirection as Mn, HUB_MAIN_RSS_BUDGET_MB as Mt, AGENT_CAP_FWD_SERVICE as N, socketDirectionBytes as Nn, HUB_RSS_BUDGET_ENV as Nt, createUdsEventBus as O, readSocketQueues as On, HEAP_RECLAIM_TRIGGER_MB as Ot, CapRouteResolver as P, socketDirectionMessages as Pn, RSS_BUDGET_REANNOUNCE_MIN_MS as Pt, encodeFrame as Q, ARRAY_BUFFER_CENSUS_COOLDOWN_MS as Qt, LocalChildClient as R, MAX_CAP_ACTIONS_PER_PEER as Rn, ZERO_LOOP_DELAY as Rt, buildLinkChain as S, SOCKET_PLANE_TOP_N as Sn, buildNpmChildEnv as St, localProviderLink as T, createSocketPlaneReader as Tn, createAddonDataPlaneFacility as Tt, PARENT_ROUTED_PROVIDER_ADDON_ID as U, createEventPlaneMeter as Un, describeRss as Ut, readMoleculerFanoutMode as V, diffCapActionCensus as Vn, createLoopDelayMeter as Vt, __resetCapUsageRegistryForTests as W, createEventPlaneReader as Wn, hubMainRssBudget as Wt, localEndpointPath as X, startRunnerHeapWatch as Xt, SocketChannel as Y, startHeapWatch as Yt, FrameDecoder as Z, strandedMb as Zt, AddonDepsManager as _, formatOutboundQueueVerdict as _n, CapabilityHandle as _t, createUdsAddonContext as a, isArrayBufferCensusReport as an, setWorkerNativeCapsChangeListener as at, brokerCallForCap as b, formatMeshQueue as bn, installManifestNativeDeps as bt, getOrInitReadinessRegistryForClient as c, emitHeapDiagnosticReport as cn, NATIVE_PROVIDER_SERVICE_INFIX as ct, clusterEventTopic as d, readHeapSpaces as dn, capBareAction as dt, ARRAY_BUFFER_CENSUS_THRESHOLD_MB as en, buildUdsNativeCapProxy as et, getBrokerEventBus as f, selectReportedSpaces as fn, capServiceName as ft, subscribePassthrough as g, createOutboundQueueGate as gn, DeviceRegistry as gt, setNodeEventInterest as h, OUTBOUND_QUEUE_THRESHOLD_BYTES as hn, serializeTypedArrays as ht, createAddonContext as i, formatArrayBufferCensus as in, mountNativeCapService as it, createUdsLoggerWithControl as j, recordSocketFrame as jn, HUB_MAIN_HEAP_WATCH_LABEL as jt, udsChildLogToWorkerEntry as k, EMPTY_SOCKET_DIRECTION as kn, HEAP_WATCH_INTERVAL_MS as kt, setHubConnected as l, formatHeapSpaces as ln, capActionName as lt, registerEventBusService as m, OUTBOUND_QUEUE_SUSTAIN_MS as mn, deserializeTypedArrays as mt, installManifestPythonDeps as n, createArrayBufferCensusGate as nn, getWorkerNativeCapProvider as nt, getWorkerDeviceRegistry as o, DECISIVE_HEAP_SPACES as on, createAddonService as ot, getMoleculerEventStats as p, OUTBOUND_QUEUE_COOLDOWN_MS as pn, parseCapAction as pt, UdsLocalTransportClient as q, reclaimIntervalMs as qt, adaptBrokerToCluster as r, createInspectorArrayBufferCensus as rn, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistry as s, HEAP_SPACE_REPORT_MIN_MB as sn, validateProviderRegistrations as st, installManifestSystemDeps as t, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO as tn, createBrokerDeviceManagerApi as tt, EVENT_TOPIC_PREFIX as u, heapSpaceField as un, capActionSuffix as ut, createKernelHwAccel as v, MESH_QUEUE_TOP_N as vn, CapabilityUnavailableError as vt, ipcParentLink as w, createSocketPlaneMeter as wn, runNpm as wt, brokerTransportLink as x, EMPTY_SOCKET_PLANE_BASELINE as xn, resolveAddonClass as xt, resolveHwAccel as y, createMeshQueueReader as yn, copyBundledNativeModules as yt, LocalChildRegistry as z, capActionLabel as zn, buildHeapSample as zt };
12274
+ export { buildNativeCapProxy as $, strandedMb as $t, createUdsLogger as A, readSocketQueues as An, HEAP_RECLAIM_TRIGGER_MB as At, UDS_NO_ROUTE_PREFIX as B, MAX_CAP_ACTIONS_PER_PEER as Bn, ZERO_LOOP_DELAY as Bt, deviceCapResolverLink as C, EMPTY_SOCKET_PLANE_BASELINE as Cn, resolveNpmInvocation as Ct, createUdsEventBridge as D, createSocketPlaneReader as Dn, createAddonDataPlaneFacility as Dt, createParentUnownedCallHandler as E, createSocketPlaneMeter as En, hostNodeIdOf as Et, callWithServiceDiscovery as F, socketDirectionBytes as Fn, HUB_RSS_BUDGET_ENV as Ft, getCapUsageRegistry as G, createEventPlaneMeter as Gn, describeRss as Gt, CapUsageRegistry as H, createCapActionCensus as Hn, createDeferredHeapWatchSink as Ht, CapRouteError as I, socketDirectionMessages as In, RSS_BUDGET_REANNOUNCE_MIN_MS as It, UdsLocalTransportServer as J, formatEventPlane as Jn, parseRssBudgetMb as Jt, createLocalTransport as K, createEventPlaneReader as Kn, hubMainRssBudget as Kt, classifyCapRoute as L, CAP_ACTION_OVERFLOW_LABEL as Ln, RSS_BUDGET_RELEASE_RATIO as Lt, AGENT_CAP_FWD_ACTION as M, createSocketDirectionCounters as Mn, HEAP_WATCH_WARN_RATIO as Mt, AGENT_CAP_FWD_SERVICE as N, recordSocketFrame as Nn, HUB_MAIN_HEAP_WATCH_LABEL as Nt, createUdsEventBus as O, diffSocketPlane as On, HEAP_RECLAIM_MIN_INTERVAL_MS as Ot, CapRouteResolver as P, sampleSocketDirection as Pn, HUB_MAIN_RSS_BUDGET_MB as Pt, encodeFrame as Q, startRunnerHeapWatch as Qt, LocalChildClient as R, CAP_ACTION_UNLABELLED as Rn, RUNNER_HEAP_WATCH_INTERVAL_MS as Rt, buildLinkChain as S, formatMeshQueue as Sn, buildNpmChildEnv as St, localProviderLink as T, SOCKET_QUEUE_TOP_N as Tn, createAddonPeerBytesFacility as Tt, PARENT_ROUTED_PROVIDER_ADDON_ID as U, diffCapActionCensus as Un, createLoopDelayMeter as Ut, readMoleculerFanoutMode as V, capActionLabel as Vn, buildHeapSample as Vt, __resetCapUsageRegistryForTests as W, EVENT_PLANE_TOP_N as Wn, createV8Reclaimer as Wt, localEndpointPath as X, shouldReclaim as Xt, SocketChannel as Y, reclaimIntervalMs as Yt, FrameDecoder as Z, startHeapWatch as Zt, AddonDepsManager as _, OUTBOUND_QUEUE_THRESHOLD_BYTES as _n, CapabilityHandle as _t, createUdsAddonContext as a, createInspectorArrayBufferCensus as an, setWorkerNativeCapsChangeListener as at, brokerCallForCap as b, MESH_QUEUE_TOP_N as bn, installManifestNativeDeps as bt, getOrInitReadinessRegistryForClient as c, DECISIVE_HEAP_SPACES as cn, NATIVE_PROVIDER_SERVICE_INFIX as ct, clusterEventTopic as d, formatHeapSpaces as dn, capBareAction as dt, ARRAY_BUFFER_CENSUS_COOLDOWN_MS as en, buildUdsNativeCapProxy as et, getBrokerEventBus as f, heapSpaceField as fn, capServiceName as ft, subscribePassthrough as g, OUTBOUND_QUEUE_SUSTAIN_MS as gn, DeviceRegistry as gt, setNodeEventInterest as h, OUTBOUND_QUEUE_COOLDOWN_MS as hn, serializeTypedArrays as ht, createAddonContext as i, createArrayBufferCensusGate as in, mountNativeCapService as it, createUdsLoggerWithControl as j, EMPTY_SOCKET_DIRECTION as jn, HEAP_WATCH_INTERVAL_MS as jt, udsChildLogToWorkerEntry as k, formatSocketPlane as kn, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as kt, setHubConnected as l, HEAP_SPACE_REPORT_MIN_MB as ln, capActionName as lt, registerEventBusService as m, selectReportedSpaces as mn, deserializeTypedArrays as mt, installManifestPythonDeps as n, ARRAY_BUFFER_CENSUS_THRESHOLD_MB as nn, getWorkerNativeCapProvider as nt, getWorkerDeviceRegistry as o, formatArrayBufferCensus as on, createAddonService as ot, getMoleculerEventStats as p, readHeapSpaces as pn, parseCapAction as pt, UdsLocalTransportClient as q, diffEventPlane as qn, nextRssBudgetState as qt, adaptBrokerToCluster as r, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO as rn, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistry as s, isArrayBufferCensusReport as sn, validateProviderRegistrations as st, installManifestSystemDeps as t, ARRAY_BUFFER_CENSUS_SUSTAIN_MS as tn, createBrokerDeviceManagerApi as tt, EVENT_TOPIC_PREFIX as u, emitHeapDiagnosticReport as un, capActionSuffix as ut, createKernelHwAccel as v, createOutboundQueueGate as vn, CapabilityUnavailableError as vt, ipcParentLink as w, SOCKET_PLANE_TOP_N as wn, runNpm as wt, brokerTransportLink as x, createMeshQueueReader as xn, resolveAddonClass as xt, resolveHwAccel as y, formatOutboundQueueVerdict as yn, copyBundledNativeModules as yt, LocalChildRegistry as z, EMPTY_CAP_ACTION_READING as zn, RUNNER_RSS_BUDGET_ENV as zt };