@camstack/system 1.2.418 → 1.2.420
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/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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- 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 +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- 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/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- 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 +2 -2
- package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- 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-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -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 +138 -18
- package/dist/builtins/system-config/system-config.addon.mjs +138 -19
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-0cPw9E0M.mjs → composition-sources-562ib2dL.mjs} +1 -1
- package/dist/{composition-sources-BVjuMvpb.js → composition-sources-OaDYklhR.js} +1 -1
- package/dist/{constants-C8T7u1_Z.mjs → constants-BeQWXybC.mjs} +1 -1
- package/dist/{constants-Aj0OBqlt.js → constants-ik5xtasS.js} +1 -1
- package/dist/{dist-DlmdESNT.mjs → dist-C0wYvAHC.mjs} +11 -0
- package/dist/{dist-DVUg8e1T.js → dist-DLHPmdoV.js} +11 -0
- package/dist/index.js +187 -19
- package/dist/index.mjs +183 -21
- package/dist/{jwt-signing-key-Jyu81jZR.js → jwt-signing-key-CWmNKLQi.js} +1 -1
- package/dist/{jwt-signing-key-CGg61A5v.mjs → jwt-signing-key-dkTVfsLI.mjs} +1 -1
- package/dist/kernel/index.d.ts +5 -1
- package/dist/kernel/moleculer/agent-event-scope.d.ts +49 -0
- package/dist/kernel/moleculer/agent-mesh-authenticator.d.ts +15 -3
- package/dist/kernel/moleculer/broker-factory.d.ts +6 -0
- package/dist/kernel/moleculer/mesh-access-middleware.d.ts +8 -4
- package/dist/kernel/moleculer/mesh-join-hold.d.ts +85 -0
- package/dist/kernel/moleculer/mesh-mtls/index.d.ts +1 -1
- package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +65 -4
- package/dist/{restore-archive-DVo5ooBt.mjs → restore-archive-Bt65Em24.mjs} +1 -1
- package/dist/{restore-archive-Bq5PpQ-H.js → restore-archive-DZeFVS2l.js} +1 -1
- package/dist/{retired-settings-keys-k_XuC-Mt.mjs → retired-settings-keys-BHlKEYNL.mjs} +1 -1
- package/dist/{retired-settings-keys-CihMX6h9.js → retired-settings-keys-DibNtLmU.js} +1 -1
- package/package.json +2 -1
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DLHPmdoV.js");
|
|
7
7
|
const require_safe_buffer$1 = require("../../safe-buffer-D3aoa-7X.js");
|
|
8
|
-
const require_constants$8 = require("../../constants-
|
|
8
|
+
const require_constants$8 = require("../../constants-ik5xtasS.js");
|
|
9
9
|
const require_x509_es = require("../../x509.es-Dzl0BbmG.js");
|
|
10
10
|
const require_src$1 = require("../../src-CnAqG6EF.js");
|
|
11
11
|
let zod = require("zod");
|
|
@@ -86720,15 +86720,66 @@ import_moleculer.default.Validators;
|
|
|
86720
86720
|
//#endregion
|
|
86721
86721
|
//#region src/kernel/moleculer/mesh-mtls/tls-tcp-transporter.ts
|
|
86722
86722
|
/**
|
|
86723
|
-
* Moleculer TCP transporter with a mutual-TLS listener (D826).
|
|
86723
|
+
* Moleculer TCP transporter with a mutual-TLS listener (D826, D848).
|
|
86724
86724
|
*
|
|
86725
86725
|
* Hub: `hubTlsOnly` — no clear TCP reader and no UDP discovery; TLS 1.3 on
|
|
86726
|
-
* `tlsPort` (default 6443) once mesh material is ready
|
|
86727
|
-
*
|
|
86728
|
-
*
|
|
86729
|
-
* is
|
|
86726
|
+
* `tlsPort` (default 6443) once mesh material is ready, and every peer is
|
|
86727
|
+
* dialled on mutual TLS too (a TLS-only hub has no clear peer to talk to).
|
|
86728
|
+
*
|
|
86729
|
+
* Enrolled agent: `hubOnlyPeers` (D848) — the hub is its ONLY mesh peer. No
|
|
86730
|
+
* clear TCP reader; the TLS listener accepts the hub's certificate only; the
|
|
86731
|
+
* writer dials the hub only; and the nodes the hub's gossip names are not
|
|
86732
|
+
* adopted, so the agent never learns, dials or broadcasts to another agent.
|
|
86733
|
+
* An agent without mesh material keeps clear TCP (it can reach only a
|
|
86734
|
+
* pre-D826 hub).
|
|
86735
|
+
*
|
|
86736
|
+
* On a TLS socket, any packet whose `sender` ≠ the peer certificate CN is
|
|
86737
|
+
* dropped. A TLS-listening node advertises its listen port in the gossip
|
|
86738
|
+
* HELLO it sends on every connection (`meshTlsPort`,
|
|
86739
|
+
* {@link MESH_TLS_PORT_HELLO_FIELD}); a peer dials it there, else on the
|
|
86740
|
+
* cluster-wide default (6443) — so two nodes on one host need not collide.
|
|
86730
86741
|
*/
|
|
86742
|
+
/** Field of the gossip HELLO payload carrying the sender's mesh-TLS listen port. */
|
|
86743
|
+
var MESH_TLS_PORT_HELLO_FIELD = "meshTlsPort";
|
|
86744
|
+
/** Moleculer packet types (`packets.PACKET_GOSSIP_*`). */
|
|
86745
|
+
var PACKET_GOSSIP_RES = "GOSSIP_RES";
|
|
86746
|
+
var PACKET_GOSSIP_HELLO = "GOSSIP_HELLO";
|
|
86747
|
+
/** The hub's node id — the only peer of a `hubOnlyPeers` agent. */
|
|
86748
|
+
var DEFAULT_HUB_NODE_ID = "hub";
|
|
86731
86749
|
var TcpTransporterBase = Transporters.TCP;
|
|
86750
|
+
/** The mesh-TLS port a gossip HELLO payload advertises, or null. */
|
|
86751
|
+
function helloMeshTlsPort(payload) {
|
|
86752
|
+
if (payload === null || typeof payload !== "object") return null;
|
|
86753
|
+
const port = Reflect.get(payload, MESH_TLS_PORT_HELLO_FIELD);
|
|
86754
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
|
|
86755
|
+
}
|
|
86756
|
+
/**
|
|
86757
|
+
* A gossip response with every node but `keep` removed. With nothing to
|
|
86758
|
+
* remove the payload is returned unchanged.
|
|
86759
|
+
*/
|
|
86760
|
+
function keepOnlyGossipNodes(payload, keep) {
|
|
86761
|
+
if (payload === null || typeof payload !== "object") return {
|
|
86762
|
+
payload,
|
|
86763
|
+
removed: []
|
|
86764
|
+
};
|
|
86765
|
+
const removed = [];
|
|
86766
|
+
const filtered = { ...payload };
|
|
86767
|
+
for (const section of ["online", "offline"]) {
|
|
86768
|
+
const entries = Reflect.get(payload, section);
|
|
86769
|
+
if (entries === null || typeof entries !== "object") continue;
|
|
86770
|
+
const kept = {};
|
|
86771
|
+
for (const [nodeId, value] of Object.entries(entries)) if (keep.has(nodeId)) kept[nodeId] = value;
|
|
86772
|
+
else removed.push(nodeId);
|
|
86773
|
+
filtered[section] = kept;
|
|
86774
|
+
}
|
|
86775
|
+
return removed.length === 0 ? {
|
|
86776
|
+
payload,
|
|
86777
|
+
removed
|
|
86778
|
+
} : {
|
|
86779
|
+
payload: filtered,
|
|
86780
|
+
removed
|
|
86781
|
+
};
|
|
86782
|
+
}
|
|
86732
86783
|
/**
|
|
86733
86784
|
* Extract `sender` from a Moleculer TCP packet without fully trusting it —
|
|
86734
86785
|
* used only to compare against the TLS peer CN.
|
|
@@ -86770,6 +86821,13 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86770
86821
|
tlsPeers = /* @__PURE__ */ new Map();
|
|
86771
86822
|
clearDisabled = false;
|
|
86772
86823
|
hubTlsOnly;
|
|
86824
|
+
hubOnlyPeers;
|
|
86825
|
+
hubNodeId;
|
|
86826
|
+
peerTlsPortOption;
|
|
86827
|
+
/** Peers already reported as not adopted (D848) — one line each. */
|
|
86828
|
+
ignoredPeers = /* @__PURE__ */ new Set();
|
|
86829
|
+
/** Node id → the mesh-TLS port its last HELLO advertised. */
|
|
86830
|
+
helloTlsPorts = /* @__PURE__ */ new Map();
|
|
86773
86831
|
/** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
|
|
86774
86832
|
clearWriterConnect = null;
|
|
86775
86833
|
writerTlsPatched = false;
|
|
@@ -86778,8 +86836,15 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86778
86836
|
this.meshTls = opts.meshTls ?? null;
|
|
86779
86837
|
this.crl = opts.crl ?? processMeshCrl();
|
|
86780
86838
|
this.hubTlsOnly = opts.hubTlsOnly === true;
|
|
86839
|
+
this.hubOnlyPeers = opts.hubOnlyPeers === true;
|
|
86840
|
+
this.hubNodeId = opts.hubNodeId ?? DEFAULT_HUB_NODE_ID;
|
|
86841
|
+
this.peerTlsPortOption = opts.peerTlsPort;
|
|
86781
86842
|
if (this.opts.tlsPort === void 0) this.opts.tlsPort = require_constants$8.DEFAULT_HUB_TLS_TCP_PORT;
|
|
86782
86843
|
}
|
|
86844
|
+
/** Clear TCP is never served: the TLS-only hub, or an agent whose only peer is the hub. */
|
|
86845
|
+
get tlsOnly() {
|
|
86846
|
+
return this.hubTlsOnly || this.hubOnlyPeers;
|
|
86847
|
+
}
|
|
86783
86848
|
startTcpServer() {
|
|
86784
86849
|
const started = super.startTcpServer();
|
|
86785
86850
|
if (this.meshTls !== null) this.patchWriterForTlsClient();
|
|
@@ -86789,8 +86854,8 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86789
86854
|
return this.Promise.resolve().then(() => {
|
|
86790
86855
|
if (this.opts.urls) return this.loadUrls();
|
|
86791
86856
|
}).then(() => this.startTcpServer()).then(() => {
|
|
86792
|
-
if (this.
|
|
86793
|
-
}).then(() => this.startTlsServerIfReady()).then(() => this.
|
|
86857
|
+
if (this.tlsOnly) this.disableClearTransport();
|
|
86858
|
+
}).then(() => this.startTlsServerIfReady()).then(() => this.tlsOnly ? this.Promise.resolve() : this.startUdpServer()).then(() => this.startTimers()).then(() => {
|
|
86794
86859
|
this.logger.info("TCP Transporter started.");
|
|
86795
86860
|
this.connected = true;
|
|
86796
86861
|
this.nodes.localNode.port = this.opts.port;
|
|
@@ -86798,17 +86863,19 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86798
86863
|
}).then(() => this.onConnected());
|
|
86799
86864
|
}
|
|
86800
86865
|
/**
|
|
86801
|
-
* Outbound dial uses mTLS when the
|
|
86802
|
-
*
|
|
86803
|
-
*
|
|
86804
|
-
* TLS
|
|
86866
|
+
* Outbound dial uses mTLS when dialling the hub node id (agent → hub), on a
|
|
86867
|
+
* TLS-only hub (it has no clear listener, so no clear peer exists to dial —
|
|
86868
|
+
* D848 closes the hub → agent clear leg), or when the peer is already known
|
|
86869
|
+
* on TLS (inbound handshake).
|
|
86805
86870
|
*/
|
|
86806
86871
|
shouldDialPeerWithTls(nodeID) {
|
|
86807
|
-
if (nodeID ===
|
|
86872
|
+
if (nodeID === this.hubNodeId) return true;
|
|
86873
|
+
if (this.hubTlsOnly) return true;
|
|
86808
86874
|
return this.tlsPeers.has(nodeID);
|
|
86809
86875
|
}
|
|
86810
|
-
|
|
86811
|
-
|
|
86876
|
+
/** The peer's advertised mesh-TLS port, else the configured dial port, else 6443. */
|
|
86877
|
+
peerTlsDialPort(nodeID) {
|
|
86878
|
+
return this.helloTlsPorts.get(nodeID) ?? this.peerTlsPortOption ?? this.opts.tlsPort ?? 6443;
|
|
86812
86879
|
}
|
|
86813
86880
|
/**
|
|
86814
86881
|
* Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
|
|
@@ -86828,13 +86895,14 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86828
86895
|
const crl = this.crl;
|
|
86829
86896
|
const self = this;
|
|
86830
86897
|
writer.connect = (nodeID) => {
|
|
86898
|
+
if (self.hubOnlyPeers && nodeID !== self.hubNodeId) return writer.Promise.reject(/* @__PURE__ */ new Error(`mesh: an enrolled agent dials only "${self.hubNodeId}", not "${nodeID}"`));
|
|
86831
86899
|
if (!self.shouldDialPeerWithTls(nodeID)) return clearConnect(nodeID);
|
|
86832
86900
|
const material = self.meshTls;
|
|
86833
86901
|
if (material === null) return clearConnect(nodeID);
|
|
86834
86902
|
const node = writer.transporter.getNode(nodeID);
|
|
86835
86903
|
if (!node) return writer.Promise.reject(/* @__PURE__ */ new Error(`Missing node info for '${nodeID}'!`));
|
|
86836
86904
|
const host = writer.transporter.getNodeAddress(node);
|
|
86837
|
-
const port = self.peerTlsDialPort();
|
|
86905
|
+
const port = self.peerTlsDialPort(nodeID);
|
|
86838
86906
|
writer.logger.debug(`Connecting (TLS) to '${nodeID}' via ${host}:${port}`);
|
|
86839
86907
|
return new writer.Promise((resolve, reject) => {
|
|
86840
86908
|
const socket = node_tls.connect({
|
|
@@ -86940,7 +87008,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86940
87008
|
}
|
|
86941
87009
|
/** The switch back: listen on the clear port again and restart UDP discovery. Idempotent. */
|
|
86942
87010
|
async enableClearTransport() {
|
|
86943
|
-
if (this.
|
|
87011
|
+
if (this.tlsOnly) return;
|
|
86944
87012
|
if (!this.clearDisabled) return;
|
|
86945
87013
|
this.clearDisabled = false;
|
|
86946
87014
|
if (this.reader !== null && this.reader.server?.listening !== true) await this.reader.listen();
|
|
@@ -86972,7 +87040,53 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86972
87040
|
this.tlsPeers.clear();
|
|
86973
87041
|
return super.disconnect();
|
|
86974
87042
|
}
|
|
87043
|
+
/**
|
|
87044
|
+
* D848: an enrolled agent adopts no node from the hub's gossip but the hub
|
|
87045
|
+
* itself, so it never learns — and therefore never dials or broadcasts to —
|
|
87046
|
+
* another agent. Everything else is Moleculer's own handling.
|
|
87047
|
+
*/
|
|
87048
|
+
receive(cmd, data, socket) {
|
|
87049
|
+
if (cmd === PACKET_GOSSIP_HELLO) this.noteHelloTlsPort(data);
|
|
87050
|
+
if (!this.hubOnlyPeers || cmd !== PACKET_GOSSIP_RES) return super.receive(cmd, data, socket);
|
|
87051
|
+
let payload;
|
|
87052
|
+
try {
|
|
87053
|
+
payload = this.broker.serializer.deserialize(data, cmd);
|
|
87054
|
+
} catch {
|
|
87055
|
+
return super.receive(cmd, data, socket);
|
|
87056
|
+
}
|
|
87057
|
+
const filtered = keepOnlyGossipNodes(payload, new Set([this.hubNodeId, this.nodeID]));
|
|
87058
|
+
if (filtered.removed.length === 0) return super.receive(cmd, data, socket);
|
|
87059
|
+
for (const nodeId of filtered.removed) {
|
|
87060
|
+
if (this.ignoredPeers.has(nodeId)) continue;
|
|
87061
|
+
this.ignoredPeers.add(nodeId);
|
|
87062
|
+
this.logger.info(`Mesh: not adopting node '${nodeId}' from the hub's gossip — an enrolled agent's only peer is the hub (D848)`);
|
|
87063
|
+
}
|
|
87064
|
+
return super.receive(cmd, this.broker.serializer.serialize(filtered.payload, cmd), socket);
|
|
87065
|
+
}
|
|
87066
|
+
/** Remember the TLS port a peer's HELLO advertises (absent: an older node → the default). */
|
|
87067
|
+
noteHelloTlsPort(data) {
|
|
87068
|
+
let payload;
|
|
87069
|
+
try {
|
|
87070
|
+
payload = this.broker.serializer.deserialize(data, PACKET_GOSSIP_HELLO);
|
|
87071
|
+
} catch {
|
|
87072
|
+
return;
|
|
87073
|
+
}
|
|
87074
|
+
const sender = payload !== null && typeof payload === "object" ? Reflect.get(payload, "sender") : null;
|
|
87075
|
+
if (typeof sender !== "string") return;
|
|
87076
|
+
const port = helloMeshTlsPort(payload);
|
|
87077
|
+
if (port === null) this.helloTlsPorts.delete(sender);
|
|
87078
|
+
else this.helloTlsPorts.set(sender, port);
|
|
87079
|
+
}
|
|
87080
|
+
/** A TLS-listening node says where in every HELLO it sends. */
|
|
87081
|
+
publish(packet) {
|
|
87082
|
+
if (packet.type === PACKET_GOSSIP_HELLO && this.tlsServer !== null) packet.payload[MESH_TLS_PORT_HELLO_FIELD] = this.opts.tlsPort;
|
|
87083
|
+
return super.publish(packet);
|
|
87084
|
+
}
|
|
86975
87085
|
onIncomingMessage(type, message, socket) {
|
|
87086
|
+
if (this.hubOnlyPeers && (socket === void 0 || !isTlsSocket(socket))) {
|
|
87087
|
+
this.logger.warn("Dropping mesh packet — an enrolled agent accepts TLS from the hub only");
|
|
87088
|
+
return;
|
|
87089
|
+
}
|
|
86976
87090
|
if (socket !== void 0 && isTlsSocket(socket)) {
|
|
86977
87091
|
const tlsSock = socket;
|
|
86978
87092
|
const identity = tlsSock.meshPeerCn ? {
|
|
@@ -87043,6 +87157,11 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
87043
87157
|
socket.destroy();
|
|
87044
87158
|
return;
|
|
87045
87159
|
}
|
|
87160
|
+
if (this.hubOnlyPeers && identity.cn !== this.hubNodeId) {
|
|
87161
|
+
this.logger.warn(`Mesh TLS client rejected — an enrolled agent accepts only '${this.hubNodeId}', not CN=${identity.cn} (D848)`);
|
|
87162
|
+
socket.destroy();
|
|
87163
|
+
return;
|
|
87164
|
+
}
|
|
87046
87165
|
socket.meshPeerCn = identity.cn;
|
|
87047
87166
|
socket.meshPeerSerial = identity.serial;
|
|
87048
87167
|
this.tlsSockets.add(socket);
|
|
@@ -87745,6 +87864,7 @@ exports.AGENT_UI_UNLOCK_VERSION = AGENT_UI_UNLOCK_VERSION;
|
|
|
87745
87864
|
exports.Errors = Errors;
|
|
87746
87865
|
exports.JoinTokenJtiStore = JoinTokenJtiStore;
|
|
87747
87866
|
exports.MESH_AUTH_MODE_FIELD = MESH_AUTH_MODE_FIELD;
|
|
87867
|
+
exports.MESH_TLS_PORT_HELLO_FIELD = MESH_TLS_PORT_HELLO_FIELD;
|
|
87748
87868
|
exports.MeshCaKeeper = MeshCaKeeper;
|
|
87749
87869
|
exports.MeshCrl = MeshCrl;
|
|
87750
87870
|
exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $r as transformSecretWrite, Er as defineCustomActions, Fr as isSecretClear, Ir as isSecretMarker, Rr as makeSecretMarker, Tr as customAction, Yr as secretTreeFromSchema, cr as BaseAddon, jr as hydrateSchema, sr as errMsg } from "../../dist-
|
|
2
|
+
import { $r as transformSecretWrite, Er as defineCustomActions, Fr as isSecretClear, Ir as isSecretMarker, Rr as makeSecretMarker, Tr as customAction, Yr as secretTreeFromSchema, cr as BaseAddon, jr as hydrateSchema, sr as errMsg } from "../../dist-C0wYvAHC.mjs";
|
|
3
3
|
import { t as require_safe_buffer } from "../../safe-buffer-B1OjL1I9.mjs";
|
|
4
|
-
import { a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, c as MESH_JOIN_TOKEN_MAX_TTL_SEC, d as CLUSTER_SECRET_REF, i as MESH_CA_FIELD, l as SERVER_AUTH_OID, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, r as MESH_CA_COMMON_NAME, s as MESH_CA_VALIDITY_DAYS, t as CLIENT_AUTH_OID, v as hashClusterSecret } from "../../constants-
|
|
4
|
+
import { a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, c as MESH_JOIN_TOKEN_MAX_TTL_SEC, d as CLUSTER_SECRET_REF, i as MESH_CA_FIELD, l as SERVER_AUTH_OID, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, r as MESH_CA_COMMON_NAME, s as MESH_CA_VALIDITY_DAYS, t as CLIENT_AUTH_OID, v as hashClusterSecret } from "../../constants-BeQWXybC.mjs";
|
|
5
5
|
import { a as KeyUsagesExtension, c as Pkcs10CertificateRequestGenerator, d as X509Certificate$1, f as X509CertificateGenerator, i as KeyUsageFlags, n as BasicConstraintsExtension, o as PemConverter, p as cryptoProvider, r as ExtendedKeyUsageExtension, s as Pkcs10CertificateRequest, t as AuthorityKeyIdentifierExtension, u as SubjectKeyIdentifierExtension } from "../../x509.es-CAjSsrkk.mjs";
|
|
6
6
|
import { t as require_src } from "../../src-hG4grMU4.mjs";
|
|
7
7
|
import { z } from "zod";
|
|
@@ -86711,15 +86711,66 @@ import_moleculer.default.Validators;
|
|
|
86711
86711
|
//#endregion
|
|
86712
86712
|
//#region src/kernel/moleculer/mesh-mtls/tls-tcp-transporter.ts
|
|
86713
86713
|
/**
|
|
86714
|
-
* Moleculer TCP transporter with a mutual-TLS listener (D826).
|
|
86714
|
+
* Moleculer TCP transporter with a mutual-TLS listener (D826, D848).
|
|
86715
86715
|
*
|
|
86716
86716
|
* Hub: `hubTlsOnly` — no clear TCP reader and no UDP discovery; TLS 1.3 on
|
|
86717
|
-
* `tlsPort` (default 6443) once mesh material is ready
|
|
86718
|
-
*
|
|
86719
|
-
*
|
|
86720
|
-
* is
|
|
86717
|
+
* `tlsPort` (default 6443) once mesh material is ready, and every peer is
|
|
86718
|
+
* dialled on mutual TLS too (a TLS-only hub has no clear peer to talk to).
|
|
86719
|
+
*
|
|
86720
|
+
* Enrolled agent: `hubOnlyPeers` (D848) — the hub is its ONLY mesh peer. No
|
|
86721
|
+
* clear TCP reader; the TLS listener accepts the hub's certificate only; the
|
|
86722
|
+
* writer dials the hub only; and the nodes the hub's gossip names are not
|
|
86723
|
+
* adopted, so the agent never learns, dials or broadcasts to another agent.
|
|
86724
|
+
* An agent without mesh material keeps clear TCP (it can reach only a
|
|
86725
|
+
* pre-D826 hub).
|
|
86726
|
+
*
|
|
86727
|
+
* On a TLS socket, any packet whose `sender` ≠ the peer certificate CN is
|
|
86728
|
+
* dropped. A TLS-listening node advertises its listen port in the gossip
|
|
86729
|
+
* HELLO it sends on every connection (`meshTlsPort`,
|
|
86730
|
+
* {@link MESH_TLS_PORT_HELLO_FIELD}); a peer dials it there, else on the
|
|
86731
|
+
* cluster-wide default (6443) — so two nodes on one host need not collide.
|
|
86721
86732
|
*/
|
|
86733
|
+
/** Field of the gossip HELLO payload carrying the sender's mesh-TLS listen port. */
|
|
86734
|
+
var MESH_TLS_PORT_HELLO_FIELD = "meshTlsPort";
|
|
86735
|
+
/** Moleculer packet types (`packets.PACKET_GOSSIP_*`). */
|
|
86736
|
+
var PACKET_GOSSIP_RES = "GOSSIP_RES";
|
|
86737
|
+
var PACKET_GOSSIP_HELLO = "GOSSIP_HELLO";
|
|
86738
|
+
/** The hub's node id — the only peer of a `hubOnlyPeers` agent. */
|
|
86739
|
+
var DEFAULT_HUB_NODE_ID = "hub";
|
|
86722
86740
|
var TcpTransporterBase = Transporters.TCP;
|
|
86741
|
+
/** The mesh-TLS port a gossip HELLO payload advertises, or null. */
|
|
86742
|
+
function helloMeshTlsPort(payload) {
|
|
86743
|
+
if (payload === null || typeof payload !== "object") return null;
|
|
86744
|
+
const port = Reflect.get(payload, MESH_TLS_PORT_HELLO_FIELD);
|
|
86745
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
|
|
86746
|
+
}
|
|
86747
|
+
/**
|
|
86748
|
+
* A gossip response with every node but `keep` removed. With nothing to
|
|
86749
|
+
* remove the payload is returned unchanged.
|
|
86750
|
+
*/
|
|
86751
|
+
function keepOnlyGossipNodes(payload, keep) {
|
|
86752
|
+
if (payload === null || typeof payload !== "object") return {
|
|
86753
|
+
payload,
|
|
86754
|
+
removed: []
|
|
86755
|
+
};
|
|
86756
|
+
const removed = [];
|
|
86757
|
+
const filtered = { ...payload };
|
|
86758
|
+
for (const section of ["online", "offline"]) {
|
|
86759
|
+
const entries = Reflect.get(payload, section);
|
|
86760
|
+
if (entries === null || typeof entries !== "object") continue;
|
|
86761
|
+
const kept = {};
|
|
86762
|
+
for (const [nodeId, value] of Object.entries(entries)) if (keep.has(nodeId)) kept[nodeId] = value;
|
|
86763
|
+
else removed.push(nodeId);
|
|
86764
|
+
filtered[section] = kept;
|
|
86765
|
+
}
|
|
86766
|
+
return removed.length === 0 ? {
|
|
86767
|
+
payload,
|
|
86768
|
+
removed
|
|
86769
|
+
} : {
|
|
86770
|
+
payload: filtered,
|
|
86771
|
+
removed
|
|
86772
|
+
};
|
|
86773
|
+
}
|
|
86723
86774
|
/**
|
|
86724
86775
|
* Extract `sender` from a Moleculer TCP packet without fully trusting it —
|
|
86725
86776
|
* used only to compare against the TLS peer CN.
|
|
@@ -86761,6 +86812,13 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86761
86812
|
tlsPeers = /* @__PURE__ */ new Map();
|
|
86762
86813
|
clearDisabled = false;
|
|
86763
86814
|
hubTlsOnly;
|
|
86815
|
+
hubOnlyPeers;
|
|
86816
|
+
hubNodeId;
|
|
86817
|
+
peerTlsPortOption;
|
|
86818
|
+
/** Peers already reported as not adopted (D848) — one line each. */
|
|
86819
|
+
ignoredPeers = /* @__PURE__ */ new Set();
|
|
86820
|
+
/** Node id → the mesh-TLS port its last HELLO advertised. */
|
|
86821
|
+
helloTlsPorts = /* @__PURE__ */ new Map();
|
|
86764
86822
|
/** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
|
|
86765
86823
|
clearWriterConnect = null;
|
|
86766
86824
|
writerTlsPatched = false;
|
|
@@ -86769,8 +86827,15 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86769
86827
|
this.meshTls = opts.meshTls ?? null;
|
|
86770
86828
|
this.crl = opts.crl ?? processMeshCrl();
|
|
86771
86829
|
this.hubTlsOnly = opts.hubTlsOnly === true;
|
|
86830
|
+
this.hubOnlyPeers = opts.hubOnlyPeers === true;
|
|
86831
|
+
this.hubNodeId = opts.hubNodeId ?? DEFAULT_HUB_NODE_ID;
|
|
86832
|
+
this.peerTlsPortOption = opts.peerTlsPort;
|
|
86772
86833
|
if (this.opts.tlsPort === void 0) this.opts.tlsPort = DEFAULT_HUB_TLS_TCP_PORT;
|
|
86773
86834
|
}
|
|
86835
|
+
/** Clear TCP is never served: the TLS-only hub, or an agent whose only peer is the hub. */
|
|
86836
|
+
get tlsOnly() {
|
|
86837
|
+
return this.hubTlsOnly || this.hubOnlyPeers;
|
|
86838
|
+
}
|
|
86774
86839
|
startTcpServer() {
|
|
86775
86840
|
const started = super.startTcpServer();
|
|
86776
86841
|
if (this.meshTls !== null) this.patchWriterForTlsClient();
|
|
@@ -86780,8 +86845,8 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86780
86845
|
return this.Promise.resolve().then(() => {
|
|
86781
86846
|
if (this.opts.urls) return this.loadUrls();
|
|
86782
86847
|
}).then(() => this.startTcpServer()).then(() => {
|
|
86783
|
-
if (this.
|
|
86784
|
-
}).then(() => this.startTlsServerIfReady()).then(() => this.
|
|
86848
|
+
if (this.tlsOnly) this.disableClearTransport();
|
|
86849
|
+
}).then(() => this.startTlsServerIfReady()).then(() => this.tlsOnly ? this.Promise.resolve() : this.startUdpServer()).then(() => this.startTimers()).then(() => {
|
|
86785
86850
|
this.logger.info("TCP Transporter started.");
|
|
86786
86851
|
this.connected = true;
|
|
86787
86852
|
this.nodes.localNode.port = this.opts.port;
|
|
@@ -86789,17 +86854,19 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86789
86854
|
}).then(() => this.onConnected());
|
|
86790
86855
|
}
|
|
86791
86856
|
/**
|
|
86792
|
-
* Outbound dial uses mTLS when the
|
|
86793
|
-
*
|
|
86794
|
-
*
|
|
86795
|
-
* TLS
|
|
86857
|
+
* Outbound dial uses mTLS when dialling the hub node id (agent → hub), on a
|
|
86858
|
+
* TLS-only hub (it has no clear listener, so no clear peer exists to dial —
|
|
86859
|
+
* D848 closes the hub → agent clear leg), or when the peer is already known
|
|
86860
|
+
* on TLS (inbound handshake).
|
|
86796
86861
|
*/
|
|
86797
86862
|
shouldDialPeerWithTls(nodeID) {
|
|
86798
|
-
if (nodeID ===
|
|
86863
|
+
if (nodeID === this.hubNodeId) return true;
|
|
86864
|
+
if (this.hubTlsOnly) return true;
|
|
86799
86865
|
return this.tlsPeers.has(nodeID);
|
|
86800
86866
|
}
|
|
86801
|
-
|
|
86802
|
-
|
|
86867
|
+
/** The peer's advertised mesh-TLS port, else the configured dial port, else 6443. */
|
|
86868
|
+
peerTlsDialPort(nodeID) {
|
|
86869
|
+
return this.helloTlsPorts.get(nodeID) ?? this.peerTlsPortOption ?? this.opts.tlsPort ?? 6443;
|
|
86803
86870
|
}
|
|
86804
86871
|
/**
|
|
86805
86872
|
* Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
|
|
@@ -86819,13 +86886,14 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86819
86886
|
const crl = this.crl;
|
|
86820
86887
|
const self = this;
|
|
86821
86888
|
writer.connect = (nodeID) => {
|
|
86889
|
+
if (self.hubOnlyPeers && nodeID !== self.hubNodeId) return writer.Promise.reject(/* @__PURE__ */ new Error(`mesh: an enrolled agent dials only "${self.hubNodeId}", not "${nodeID}"`));
|
|
86822
86890
|
if (!self.shouldDialPeerWithTls(nodeID)) return clearConnect(nodeID);
|
|
86823
86891
|
const material = self.meshTls;
|
|
86824
86892
|
if (material === null) return clearConnect(nodeID);
|
|
86825
86893
|
const node = writer.transporter.getNode(nodeID);
|
|
86826
86894
|
if (!node) return writer.Promise.reject(/* @__PURE__ */ new Error(`Missing node info for '${nodeID}'!`));
|
|
86827
86895
|
const host = writer.transporter.getNodeAddress(node);
|
|
86828
|
-
const port = self.peerTlsDialPort();
|
|
86896
|
+
const port = self.peerTlsDialPort(nodeID);
|
|
86829
86897
|
writer.logger.debug(`Connecting (TLS) to '${nodeID}' via ${host}:${port}`);
|
|
86830
86898
|
return new writer.Promise((resolve, reject) => {
|
|
86831
86899
|
const socket = tls.connect({
|
|
@@ -86931,7 +86999,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86931
86999
|
}
|
|
86932
87000
|
/** The switch back: listen on the clear port again and restart UDP discovery. Idempotent. */
|
|
86933
87001
|
async enableClearTransport() {
|
|
86934
|
-
if (this.
|
|
87002
|
+
if (this.tlsOnly) return;
|
|
86935
87003
|
if (!this.clearDisabled) return;
|
|
86936
87004
|
this.clearDisabled = false;
|
|
86937
87005
|
if (this.reader !== null && this.reader.server?.listening !== true) await this.reader.listen();
|
|
@@ -86963,7 +87031,53 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86963
87031
|
this.tlsPeers.clear();
|
|
86964
87032
|
return super.disconnect();
|
|
86965
87033
|
}
|
|
87034
|
+
/**
|
|
87035
|
+
* D848: an enrolled agent adopts no node from the hub's gossip but the hub
|
|
87036
|
+
* itself, so it never learns — and therefore never dials or broadcasts to —
|
|
87037
|
+
* another agent. Everything else is Moleculer's own handling.
|
|
87038
|
+
*/
|
|
87039
|
+
receive(cmd, data, socket) {
|
|
87040
|
+
if (cmd === PACKET_GOSSIP_HELLO) this.noteHelloTlsPort(data);
|
|
87041
|
+
if (!this.hubOnlyPeers || cmd !== PACKET_GOSSIP_RES) return super.receive(cmd, data, socket);
|
|
87042
|
+
let payload;
|
|
87043
|
+
try {
|
|
87044
|
+
payload = this.broker.serializer.deserialize(data, cmd);
|
|
87045
|
+
} catch {
|
|
87046
|
+
return super.receive(cmd, data, socket);
|
|
87047
|
+
}
|
|
87048
|
+
const filtered = keepOnlyGossipNodes(payload, new Set([this.hubNodeId, this.nodeID]));
|
|
87049
|
+
if (filtered.removed.length === 0) return super.receive(cmd, data, socket);
|
|
87050
|
+
for (const nodeId of filtered.removed) {
|
|
87051
|
+
if (this.ignoredPeers.has(nodeId)) continue;
|
|
87052
|
+
this.ignoredPeers.add(nodeId);
|
|
87053
|
+
this.logger.info(`Mesh: not adopting node '${nodeId}' from the hub's gossip — an enrolled agent's only peer is the hub (D848)`);
|
|
87054
|
+
}
|
|
87055
|
+
return super.receive(cmd, this.broker.serializer.serialize(filtered.payload, cmd), socket);
|
|
87056
|
+
}
|
|
87057
|
+
/** Remember the TLS port a peer's HELLO advertises (absent: an older node → the default). */
|
|
87058
|
+
noteHelloTlsPort(data) {
|
|
87059
|
+
let payload;
|
|
87060
|
+
try {
|
|
87061
|
+
payload = this.broker.serializer.deserialize(data, PACKET_GOSSIP_HELLO);
|
|
87062
|
+
} catch {
|
|
87063
|
+
return;
|
|
87064
|
+
}
|
|
87065
|
+
const sender = payload !== null && typeof payload === "object" ? Reflect.get(payload, "sender") : null;
|
|
87066
|
+
if (typeof sender !== "string") return;
|
|
87067
|
+
const port = helloMeshTlsPort(payload);
|
|
87068
|
+
if (port === null) this.helloTlsPorts.delete(sender);
|
|
87069
|
+
else this.helloTlsPorts.set(sender, port);
|
|
87070
|
+
}
|
|
87071
|
+
/** A TLS-listening node says where in every HELLO it sends. */
|
|
87072
|
+
publish(packet) {
|
|
87073
|
+
if (packet.type === PACKET_GOSSIP_HELLO && this.tlsServer !== null) packet.payload[MESH_TLS_PORT_HELLO_FIELD] = this.opts.tlsPort;
|
|
87074
|
+
return super.publish(packet);
|
|
87075
|
+
}
|
|
86966
87076
|
onIncomingMessage(type, message, socket) {
|
|
87077
|
+
if (this.hubOnlyPeers && (socket === void 0 || !isTlsSocket(socket))) {
|
|
87078
|
+
this.logger.warn("Dropping mesh packet — an enrolled agent accepts TLS from the hub only");
|
|
87079
|
+
return;
|
|
87080
|
+
}
|
|
86967
87081
|
if (socket !== void 0 && isTlsSocket(socket)) {
|
|
86968
87082
|
const tlsSock = socket;
|
|
86969
87083
|
const identity = tlsSock.meshPeerCn ? {
|
|
@@ -87034,6 +87148,11 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
87034
87148
|
socket.destroy();
|
|
87035
87149
|
return;
|
|
87036
87150
|
}
|
|
87151
|
+
if (this.hubOnlyPeers && identity.cn !== this.hubNodeId) {
|
|
87152
|
+
this.logger.warn(`Mesh TLS client rejected — an enrolled agent accepts only '${this.hubNodeId}', not CN=${identity.cn} (D848)`);
|
|
87153
|
+
socket.destroy();
|
|
87154
|
+
return;
|
|
87155
|
+
}
|
|
87037
87156
|
socket.meshPeerCn = identity.cn;
|
|
87038
87157
|
socket.meshPeerSerial = identity.serial;
|
|
87039
87158
|
this.tlsSockets.add(socket);
|
|
@@ -87728,4 +87847,4 @@ function readJwtExp(token) {
|
|
|
87728
87847
|
return typeof exp === "number" ? exp : 0;
|
|
87729
87848
|
}
|
|
87730
87849
|
//#endregion
|
|
87731
|
-
export {
|
|
87850
|
+
export { AGENT_UI_UNLOCK_TTL_MS as A, isMeshAuthMode as B, generateMeshCa as C, loadMeshCa as D, issueMeshLeafFromCsr as E, parseAgentDashboardOrigins as F, setProcessMeshEnforcer as G, requestProcessMeshEnforcement as H, verifyAgentUiUnlockTicket as I, parseCidrList as J, createCidrMatcher as K, MESH_AUTH_MODE_FIELD as L, UnlockTicketJtiStore as M, isAllowedAgentDashboardReturnTo as N, AGENT_DASHBOARD_ORIGINS_FIELD as O, mintAgentUiUnlockTicket as P, getProcessMeshAuthMode as R, exportMeshPrivateKeyPem as S, SYSTEM_CONFIG_CLUSTER_SECTION_ID, SystemConfigAddon, SystemConfigAddon as default, issueMeshLeafForNode as T, setProcessMeshAuthMode as U, parseMeshAuthMode as V, setProcessMeshAuthStatusSource as W, readCidrSetting as Y, getProcessMeshCaState as _, ServiceBroker as a, caFingerprintCompact as b, JoinTokenJtiStore as c, verifyMeshJoinToken as d, getProcessNodeMeshTransport as f, MeshCaKeeper as g, processMeshCrl as h, Errors as i, AGENT_UI_UNLOCK_VERSION as j, AGENT_UI_UNLOCK_DOMAIN as k, mintMeshJoinToken as l, MeshCrl as m, MESH_TLS_PORT_HELLO_FIELD as n, setProcessMeshTlsEnabler as o, setProcessMeshPeerTransportLookup as p, normalizeIp as q, TlsTcpTransporter as r, enrollMeshNode as s, systemConfigMeshActions as t, processJoinTokenJtiStore as u, requireProcessMeshCa as v, generateMeshKeyPair as w, createMeshCsrPem as x, caFingerprint as y, getProcessMeshAuthStatus as z };
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DLHPmdoV.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Xt as logDestinationCapability, cr as BaseAddon } from "../../dist-
|
|
1
|
+
import { Xt as logDestinationCapability, cr as BaseAddon } from "../../dist-C0wYvAHC.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { er as compositionSourceKey, sr as errMsg } from "./dist-
|
|
1
|
+
import { er as compositionSourceKey, sr as errMsg } from "./dist-C0wYvAHC.mjs";
|
|
2
2
|
//#region src/builtins/core-blocks/composition-sources.ts
|
|
3
3
|
/**
|
|
4
4
|
* Describe what a composition reads and writes for SAVE-time validation
|
|
@@ -36989,6 +36989,17 @@ var BatteryStatusSchema = z.object({
|
|
|
36989
36989
|
*/
|
|
36990
36990
|
lastContactAt: z.number().optional(),
|
|
36991
36991
|
/**
|
|
36992
|
+
* Ms epoch of the last COMPLETE scheduled refresh — every fact the provider
|
|
36993
|
+
* holds for the device (level, charging, link, device info, state slices) and
|
|
36994
|
+
* its cached snapshot, read together. Absent = never since this slice was
|
|
36995
|
+
* born. Unlike `lastUpdated` (any write, even a push of one field) this is
|
|
36996
|
+
* the staleness an operator can trust: the provider owns the device fact and
|
|
36997
|
+
* so owns the window on its age (D224, D847). Not a clock in the
|
|
36998
|
+
* runtime-state-durability sense: it moves once per window and must survive
|
|
36999
|
+
* a restart, or every restart would owe the camera a wake.
|
|
37000
|
+
*/
|
|
37001
|
+
lastFullRefreshAt: z.number().optional(),
|
|
37002
|
+
/**
|
|
36992
37003
|
* True when the source is a BINARY low-battery indicator (HA
|
|
36993
37004
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
36994
37005
|
* charge level — `percentage` is then a coarse stand-in (100 = normal,
|
|
@@ -36989,6 +36989,17 @@ var BatteryStatusSchema = zod.z.object({
|
|
|
36989
36989
|
*/
|
|
36990
36990
|
lastContactAt: zod.z.number().optional(),
|
|
36991
36991
|
/**
|
|
36992
|
+
* Ms epoch of the last COMPLETE scheduled refresh — every fact the provider
|
|
36993
|
+
* holds for the device (level, charging, link, device info, state slices) and
|
|
36994
|
+
* its cached snapshot, read together. Absent = never since this slice was
|
|
36995
|
+
* born. Unlike `lastUpdated` (any write, even a push of one field) this is
|
|
36996
|
+
* the staleness an operator can trust: the provider owns the device fact and
|
|
36997
|
+
* so owns the window on its age (D224, D847). Not a clock in the
|
|
36998
|
+
* runtime-state-durability sense: it moves once per window and must survive
|
|
36999
|
+
* a restart, or every restart would owe the camera a wake.
|
|
37000
|
+
*/
|
|
37001
|
+
lastFullRefreshAt: zod.z.number().optional(),
|
|
37002
|
+
/**
|
|
36992
37003
|
* True when the source is a BINARY low-battery indicator (HA
|
|
36993
37004
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
36994
37005
|
* charge level — `percentage` is then a coarse stand-in (100 = normal,
|