@serfab/cadre-host 1.13.0 → 1.14.0
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/README.md +332 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +301 -505
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/join-output.d.ts +56 -0
- package/dist/bin/join-output.js +92 -0
- package/dist/bin/join-output.js.map +1 -0
- package/dist/bin/nat-output.d.ts +49 -0
- package/dist/bin/nat-output.js +127 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +44 -0
- package/dist/hosted/claim-details.js +66 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +318 -0
- package/dist/hosted/hosted-node-service.js +666 -0
- package/dist/hosted/hosted-node-service.js.map +1 -0
- package/dist/hosted/hosted-node-store.d.ts +40 -0
- package/dist/hosted/hosted-node-store.js +126 -0
- package/dist/hosted/hosted-node-store.js.map +1 -0
- package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
- package/dist/hosted/hosted-node-supervisor.js +396 -0
- package/dist/hosted/hosted-node-supervisor.js.map +1 -0
- package/dist/hosted/hosted-node-watcher.d.ts +83 -0
- package/dist/hosted/hosted-node-watcher.js +224 -0
- package/dist/hosted/hosted-node-watcher.js.map +1 -0
- package/dist/hosted/index.d.ts +16 -0
- package/dist/hosted/index.js +13 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/hosted/node-status.d.ts +35 -0
- package/dist/hosted/node-status.js +61 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +134 -0
- package/dist/hosted/types.js +22 -0
- package/dist/hosted/types.js.map +1 -0
- package/dist/index.d.ts +12 -24
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/installer/config.d.ts +4 -34
- package/dist/installer/config.js +3 -18
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +7 -19
- package/dist/installer/index.js +14 -69
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/paths.d.ts +0 -2
- package/dist/installer/paths.js +0 -4
- package/dist/installer/paths.js.map +1 -1
- package/dist/installer/wizard.d.ts +0 -7
- package/dist/installer/wizard.js +1 -8
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/address-resolver.d.ts +18 -17
- package/dist/nat/address-resolver.js +79 -25
- package/dist/nat/address-resolver.js.map +1 -1
- package/dist/nat/address-watch.d.ts +37 -0
- package/dist/nat/address-watch.js +84 -0
- package/dist/nat/address-watch.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +2 -1
- package/dist/nat/ddns/duckdns.js +5 -1
- package/dist/nat/ddns/duckdns.js.map +1 -1
- package/dist/nat/ddns/index.d.ts +6 -0
- package/dist/nat/ddns/index.js.map +1 -1
- package/dist/nat/index.d.ts +12 -9
- package/dist/nat/index.js +6 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +203 -77
- package/dist/nat/nat-service.js +602 -237
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +114 -30
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/port-mapper.d.ts +83 -79
- package/dist/nat/port-mapper.js +148 -189
- package/dist/nat/port-mapper.js.map +1 -1
- package/dist/nat/reachability.d.ts +31 -15
- package/dist/nat/reachability.js +97 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +78 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
- package/dist/orchestrator/host-process-orchestrator.js +78 -218
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
- package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
- package/dist/orchestrator/identity-file.js.map +1 -0
- package/dist/orchestrator/index.d.ts +2 -3
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +6 -6
- package/dist/orchestrator/node-identity.js +7 -7
- package/dist/orchestrator/node-identity.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +5 -6
- package/dist/orchestrator/port-allocator.js +9 -11
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +3 -5
- package/dist/orchestrator/state-store.js +2 -10
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +33 -22
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/error-handler.d.ts +5 -25
- package/dist/server/error-handler.js +11 -74
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +7 -11
- package/dist/server/events/types.js +2 -1
- package/dist/server/events/types.js.map +1 -1
- package/dist/server/index.d.ts +11 -37
- package/dist/server/index.js +30 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/hosted-nodes.d.ts +28 -0
- package/dist/server/routes/hosted-nodes.js +68 -0
- package/dist/server/routes/hosted-nodes.js.map +1 -0
- package/dist/server/routes/nat.d.ts +4 -1
- package/dist/server/routes/nat.js +13 -2
- package/dist/server/routes/nat.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +6 -20
- package/dist/server/routes/nodes.js +7 -86
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/routes/settings.d.ts +2 -7
- package/dist/server/routes/settings.js +3 -7
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +1 -11
- package/dist/server/routes/status.js.map +1 -1
- package/dist/server/server.js +8 -8
- package/dist/server/settings-store.d.ts +2 -2
- package/dist/server/settings-store.js +2 -2
- package/dist/server/static.js +1 -2
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-4FWuU8ye.js +19 -0
- package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
- package/dist/ui/assets/index-Di9Q4d01.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/store.d.ts +1 -1
- package/dist/update/store.js +1 -1
- package/dist/update/types.d.ts +1 -1
- package/dist/update/types.js +1 -1
- package/package.json +5 -6
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
- package/src/__tests__/orchestrator-ports.test.ts +124 -143
- package/src/__tests__/orchestrator-push.test.ts +138 -146
- package/src/__tests__/orchestrator.test.ts +1019 -1063
- package/src/bin/host.ts +368 -553
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +177 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
- package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +96 -0
- package/src/hosted/hosted-node-service.ts +795 -0
- package/src/hosted/hosted-node-store.ts +144 -0
- package/src/hosted/hosted-node-supervisor.ts +439 -0
- package/src/hosted/hosted-node-watcher.ts +250 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +71 -0
- package/src/hosted/types.ts +153 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +5 -86
- package/src/installer/__tests__/wizard.test.ts +56 -64
- package/src/installer/config.ts +135 -175
- package/src/installer/index.ts +15 -83
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +87 -103
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +433 -408
- package/src/nat/__tests__/nat-store.test.ts +127 -93
- package/src/nat/__tests__/port-mapper.test.ts +30 -191
- package/src/nat/__tests__/reachability.test.ts +96 -13
- package/src/nat/address-resolver.ts +94 -52
- package/src/nat/address-watch.ts +92 -0
- package/src/nat/ddns/duckdns.ts +5 -1
- package/src/nat/ddns/index.ts +6 -0
- package/src/nat/index.ts +87 -71
- package/src/nat/nat-service.ts +889 -501
- package/src/nat/nat-store.ts +237 -154
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +134 -25
- package/src/nat/types.ts +209 -159
- package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +81 -243
- package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
- package/src/orchestrator/index.ts +9 -10
- package/src/orchestrator/node-identity.ts +41 -41
- package/src/orchestrator/port-allocator.ts +71 -73
- package/src/orchestrator/state-store.ts +76 -90
- package/src/orchestrator/types.ts +34 -23
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +81 -118
- package/src/server/__tests__/fakes.ts +40 -44
- package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
- package/src/server/__tests__/nodes-route.test.ts +114 -283
- package/src/server/__tests__/publishers.test.ts +193 -180
- package/src/server/__tests__/server.smoke.test.ts +134 -223
- package/src/server/__tests__/settings-route.test.ts +179 -195
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/status-route.test.ts +82 -121
- package/src/server/error-handler.ts +112 -178
- package/src/server/events/types.ts +20 -24
- package/src/server/index.ts +223 -267
- package/src/server/routes/hosted-nodes.ts +83 -0
- package/src/server/routes/nat.ts +75 -63
- package/src/server/routes/nodes.ts +96 -188
- package/src/server/routes/settings.ts +115 -123
- package/src/server/routes/status.ts +102 -134
- package/src/server/server.ts +8 -8
- package/src/server/settings-store.ts +2 -2
- package/src/server/static.ts +1 -2
- package/src/update/index.ts +2 -2
- package/src/update/store.ts +1 -1
- package/src/update/types.ts +1 -1
- package/dist/auth/duration.d.ts +0 -1
- package/dist/auth/duration.js +0 -32
- package/dist/auth/duration.js.map +0 -1
- package/dist/auth/index.d.ts +0 -11
- package/dist/auth/index.js +0 -9
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/self-label.d.ts +0 -36
- package/dist/auth/self-label.js +0 -57
- package/dist/auth/self-label.js.map +0 -1
- package/dist/auth/trust-circle-store.d.ts +0 -26
- package/dist/auth/trust-circle-store.js +0 -129
- package/dist/auth/trust-circle-store.js.map +0 -1
- package/dist/auth/trust-circle.d.ts +0 -156
- package/dist/auth/trust-circle.js +0 -307
- package/dist/auth/trust-circle.js.map +0 -1
- package/dist/auth/types.d.ts +0 -84
- package/dist/auth/types.js +0 -24
- package/dist/auth/types.js.map +0 -1
- package/dist/donation/donation-service.d.ts +0 -389
- package/dist/donation/donation-service.js +0 -828
- package/dist/donation/donation-service.js.map +0 -1
- package/dist/donation/donation-store.d.ts +0 -51
- package/dist/donation/donation-store.js +0 -152
- package/dist/donation/donation-store.js.map +0 -1
- package/dist/donation/donation-supervisor.js +0 -364
- package/dist/donation/donation-supervisor.js.map +0 -1
- package/dist/donation/grant-service.d.ts +0 -80
- package/dist/donation/grant-service.js +0 -210
- package/dist/donation/grant-service.js.map +0 -1
- package/dist/donation/grant-store.d.ts +0 -38
- package/dist/donation/grant-store.js +0 -135
- package/dist/donation/grant-store.js.map +0 -1
- package/dist/donation/index.d.ts +0 -20
- package/dist/donation/index.js +0 -17
- package/dist/donation/index.js.map +0 -1
- package/dist/donation/types.d.ts +0 -224
- package/dist/donation/types.js +0 -40
- package/dist/donation/types.js.map +0 -1
- package/dist/installer/identity.js.map +0 -1
- package/dist/owner/index.d.ts +0 -11
- package/dist/owner/index.js +0 -11
- package/dist/owner/index.js.map +0 -1
- package/dist/owner/owner-node-client.d.ts +0 -110
- package/dist/owner/owner-node-client.js +0 -184
- package/dist/owner/owner-node-client.js.map +0 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
- package/dist/server/routes/bootstrap-node-validation.js +0 -197
- package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
- package/dist/server/routes/grants-admin.d.ts +0 -29
- package/dist/server/routes/grants-admin.js +0 -58
- package/dist/server/routes/grants-admin.js.map +0 -1
- package/dist/server/routes/grants.d.ts +0 -28
- package/dist/server/routes/grants.js +0 -165
- package/dist/server/routes/grants.js.map +0 -1
- package/dist/server/routes/provision-request-validation.d.ts +0 -45
- package/dist/server/routes/provision-request-validation.js +0 -97
- package/dist/server/routes/provision-request-validation.js.map +0 -1
- package/dist/server/routes/strands.d.ts +0 -20
- package/dist/server/routes/strands.js +0 -38
- package/dist/server/routes/strands.js.map +0 -1
- package/dist/server/routes/trust-circle.d.ts +0 -17
- package/dist/server/routes/trust-circle.js +0 -35
- package/dist/server/routes/trust-circle.js.map +0 -1
- package/dist/strands/index.d.ts +0 -7
- package/dist/strands/index.js +0 -6
- package/dist/strands/index.js.map +0 -1
- package/dist/strands/strand-service.d.ts +0 -50
- package/dist/strands/strand-service.js +0 -103
- package/dist/strands/strand-service.js.map +0 -1
- package/dist/strands/types.d.ts +0 -93
- package/dist/strands/types.js +0 -23
- package/dist/strands/types.js.map +0 -1
- package/dist/ui/assets/index-C6nELNfC.js +0 -23
- package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
- package/dist/ui/assets/index-TtIYSeZO.css +0 -1
- package/src/__tests__/cli-invite.smoke.test.ts +0 -192
- package/src/__tests__/host-owner.smoke.test.ts +0 -123
- package/src/__tests__/orchestrator-owner.test.ts +0 -234
- package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
- package/src/auth/__tests__/duration.test.ts +0 -28
- package/src/auth/__tests__/self-label.test.ts +0 -85
- package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
- package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
- package/src/auth/__tests__/trust-circle.test.ts +0 -445
- package/src/auth/duration.ts +0 -33
- package/src/auth/index.ts +0 -21
- package/src/auth/self-label.ts +0 -74
- package/src/auth/trust-circle-store.ts +0 -155
- package/src/auth/trust-circle.ts +0 -395
- package/src/auth/types.ts +0 -99
- package/src/donation/__tests__/donation-service.test.ts +0 -1193
- package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
- package/src/donation/__tests__/grant-service.test.ts +0 -213
- package/src/donation/__tests__/grant-store.test.ts +0 -113
- package/src/donation/donation-service.ts +0 -1033
- package/src/donation/donation-store.ts +0 -176
- package/src/donation/donation-supervisor.ts +0 -411
- package/src/donation/grant-service.ts +0 -252
- package/src/donation/grant-store.ts +0 -152
- package/src/donation/index.ts +0 -65
- package/src/donation/types.ts +0 -260
- package/src/owner/__tests__/owner-node-client.test.ts +0 -194
- package/src/owner/index.ts +0 -12
- package/src/owner/owner-node-client.ts +0 -259
- package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
- package/src/server/__tests__/grants-admin-route.test.ts +0 -171
- package/src/server/__tests__/grants-route.test.ts +0 -394
- package/src/server/__tests__/strands-route.test.ts +0 -234
- package/src/server/routes/bootstrap-node-validation.ts +0 -206
- package/src/server/routes/grants-admin.ts +0 -74
- package/src/server/routes/grants.ts +0 -193
- package/src/server/routes/provision-request-validation.ts +0 -105
- package/src/server/routes/strands.ts +0 -54
- package/src/server/routes/trust-circle.ts +0 -49
- package/src/strands/__tests__/strand-service.test.ts +0 -165
- package/src/strands/index.ts +0 -14
- package/src/strands/strand-service.ts +0 -135
- package/src/strands/types.ts +0 -102
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the QR code of a node waiting to be claimed carries, and the address list behind it.
|
|
3
|
+
* `HostedNodeService.claimDetails` reads the child's `/status` and hands it here.
|
|
4
|
+
*/
|
|
5
|
+
import { multiaddr, CODE_IP4, CODE_IP6, CODE_P2P_CIRCUIT } from '@multiformats/multiaddr';
|
|
6
|
+
import { encodeNodeClaimPayload } from '@serfab/cadre-core';
|
|
7
|
+
import { HostedNodeError } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* The claim details for a node whose `/status` answered. `503 node_unavailable` while the
|
|
10
|
+
* node reports no address a phone could dial (callers poll).
|
|
11
|
+
*/
|
|
12
|
+
export function buildClaimDetails(input) {
|
|
13
|
+
const { id, status, addresses } = input;
|
|
14
|
+
const multiaddrs = dialAddressesFor(input);
|
|
15
|
+
if (multiaddrs.length === 0) {
|
|
16
|
+
throw new HostedNodeError('node_unavailable', `Hosted node ${id} reports no address a phone could dial yet`);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
payload: encodeNodeClaimPayload({ peerId: status.peerId, multiaddrs, secret: input.secret }),
|
|
20
|
+
peerId: status.peerId,
|
|
21
|
+
multiaddrs,
|
|
22
|
+
reachability: addresses.getStatus().nodes.find((n) => n.nodeId === id) ?? null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The addresses the QR code names, in dial order: the node's public addresses
|
|
27
|
+
* from the NAT layer with the peer id appended (a phone away from home needs
|
|
28
|
+
* these), then the node's own non-loopback addresses as `/status` reports them
|
|
29
|
+
* (they already carry `/p2p/`; a phone at home dials these). Loopback and relay
|
|
30
|
+
* entries are dropped — nothing off this machine can use the former, and no
|
|
31
|
+
* hosted node holds a relay reservation today.
|
|
32
|
+
*/
|
|
33
|
+
function dialAddressesFor({ id, status, ports, addresses }) {
|
|
34
|
+
const publicAddrs = ports
|
|
35
|
+
? addresses.publicAddressesFor(id, ports).map((a) => `${a}/p2p/${status.peerId}`)
|
|
36
|
+
: [];
|
|
37
|
+
const lanAddrs = status.multiaddrs.filter((a) => isDialableFromLan(a));
|
|
38
|
+
return [...new Set([...publicAddrs, ...lanAddrs])];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether an address the node reports is one a phone on the host's LAN can dial:
|
|
42
|
+
* not loopback, not the unspecified address (libp2p expands a `0.0.0.0` listen into
|
|
43
|
+
* interface addresses, but a raw one names nothing to dial), not a relay circuit,
|
|
44
|
+
* and parsable. A DNS address passes (it names something off this machine); an
|
|
45
|
+
* address with no host component at all is dropped.
|
|
46
|
+
*/
|
|
47
|
+
function isDialableFromLan(addr) {
|
|
48
|
+
let components;
|
|
49
|
+
try {
|
|
50
|
+
components = multiaddr(addr).getComponents();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (components.some((c) => c.code === CODE_P2P_CIRCUIT))
|
|
56
|
+
return false;
|
|
57
|
+
const host = components[0];
|
|
58
|
+
if (!host?.value)
|
|
59
|
+
return false;
|
|
60
|
+
if (host.code === CODE_IP4)
|
|
61
|
+
return !host.value.startsWith('127.') && host.value !== '0.0.0.0';
|
|
62
|
+
if (host.code === CODE_IP6)
|
|
63
|
+
return host.value !== '::1' && host.value !== '::';
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=claim-details.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-details.js","sourceRoot":"","sources":["../../src/hosted/claim-details.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA8B7C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAwB;IACxD,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACxC,MAAM,UAAU,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,eAAe,EAAE,4CAA4C,CAAC,CAAC;IAC/G,CAAC;IACD,OAAO;QACL,OAAO,EAAE,sBAAsB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5F,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU;QACV,YAAY,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,IAAI,IAAI;KAC/E,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAqB;IAC3E,MAAM,WAAW,GAAG,KAAK;QACvB,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACjF,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,UAAqE,CAAC;IAC1E,IAAI,CAAC;QACH,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAC;IACtE,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,KAAK;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;IAC9F,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC/E,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import type { Orchestrator, OrchestratorCreateResult } from '@serfab/cadre-provider';
|
|
2
|
+
import type { HostedSpawnRequest, ManagedNodeInfo, NodeStateListener } from '../orchestrator/types.js';
|
|
3
|
+
import { type ClaimDetails, type HostedNodeAddressSource } from './claim-details.js';
|
|
4
|
+
import type { HostedNodeStore } from './hosted-node-store.js';
|
|
5
|
+
import type { HostedNodeChange, HostedNodeChangeListener, HostedNodeStatus, HostedNodeView } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Age after which a record still stuck in `spawning` is marked `error` and its
|
|
8
|
+
* child, if any, reclaimed: the host wrote the row before starting the child, and
|
|
9
|
+
* a crash/kill in that window (or during the spawn itself) leaves nothing to
|
|
10
|
+
* advance it across a restart. A real spawn (identity key read/gen, port
|
|
11
|
+
* allocation, process spawn) completes in well under a second normally, so 5
|
|
12
|
+
* minutes is generously past "any plausible spawn" without risking a false reap
|
|
13
|
+
* of one still genuinely in flight under heavy load.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HOSTED_NODE_SPAWNING_TTL_MS: number;
|
|
16
|
+
/** How often the stuck-`spawning` reap runs while cadre-host is up. 5 minutes. */
|
|
17
|
+
export declare const HOSTED_NODE_REAP_SWEEP_MS: number;
|
|
18
|
+
/**
|
|
19
|
+
* Outcome of {@link HostedNodeService.respawn}:
|
|
20
|
+
*
|
|
21
|
+
* - `respawned` — a new child is up and the record now names its handles.
|
|
22
|
+
* - `abandoned` — the record was removed, or went `error`, while the spawn was in
|
|
23
|
+
* flight. The ending wins: the record is left exactly as the ending wrote it and
|
|
24
|
+
* the new child is cleaned up. `status` is the status that won, absent when the
|
|
25
|
+
* row is gone entirely.
|
|
26
|
+
*/
|
|
27
|
+
export type RespawnResult = {
|
|
28
|
+
outcome: 'respawned';
|
|
29
|
+
node: HostedNodeView;
|
|
30
|
+
} | {
|
|
31
|
+
outcome: 'abandoned';
|
|
32
|
+
status?: HostedNodeStatus;
|
|
33
|
+
};
|
|
34
|
+
/** Options for {@link HostedNodeService.respawn}. */
|
|
35
|
+
export interface RespawnOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Whether this respawn spends one of the record's attempts. Default true: a crash
|
|
38
|
+
* respawn does, since the supervisor's backoff and give-up read the count. A
|
|
39
|
+
* deliberate restart of a running node (`HostedNodeSupervisor.restart`) passes false
|
|
40
|
+
* and leaves both the counters and `updatedAt` alone.
|
|
41
|
+
*/
|
|
42
|
+
countAttempt?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Orchestrator capabilities the service needs beyond the base `Orchestrator`.
|
|
46
|
+
* `createContainer` takes the claim secret. The rest are optional — only
|
|
47
|
+
* `HostProcessOrchestrator` implements them; absent on a test double or a future
|
|
48
|
+
* orchestrator, the cleanup degrades to terminalizing the record with nothing to
|
|
49
|
+
* reclaim, and the claim details carry LAN addresses only.
|
|
50
|
+
*/
|
|
51
|
+
export interface HostedNodeOrchestrator extends Orchestrator {
|
|
52
|
+
createContainer(request: HostedSpawnRequest): Promise<OrchestratorCreateResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a spawn's friendly containerId (== a hosted node's id) back to its
|
|
55
|
+
* current dockerId, so a stuck-`spawning` reap can find and reclaim a child that
|
|
56
|
+
* was actually spawned before the host died.
|
|
57
|
+
*/
|
|
58
|
+
resolveDockerId?(containerId: string): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Remove the working directory a spawn created for `containerId` when no handle
|
|
61
|
+
* owns it — the cleanup of last resort for a record that never got a `dockerId`.
|
|
62
|
+
* Returns whether anything was removed. Refuses when a handle still resolves
|
|
63
|
+
* (that child's directory belongs to `removeContainer`).
|
|
64
|
+
*/
|
|
65
|
+
reclaimWorkdir?(containerId: string): boolean;
|
|
66
|
+
/** The node's handle, for the ports its public addresses are built from. */
|
|
67
|
+
getNode?(idOrDockerId: string): ManagedNodeInfo | undefined;
|
|
68
|
+
/** The exit/start signal the status watcher follows. */
|
|
69
|
+
onStateChange?(listener: NodeStateListener): () => void;
|
|
70
|
+
}
|
|
71
|
+
/** What `join` takes: nothing for a node waiting to be claimed, or the invitation the node redeems. */
|
|
72
|
+
export interface JoinOptions {
|
|
73
|
+
/** An encoded cadre invitation (`encodeCadreInvitation`), as the owner's app copies it. */
|
|
74
|
+
invitation?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Constructor options. */
|
|
77
|
+
export interface HostedNodeServiceOptions {
|
|
78
|
+
/** Orchestrator that spawns/stops the hosted child processes. */
|
|
79
|
+
orchestrator: HostedNodeOrchestrator;
|
|
80
|
+
/** Persistent record store (`hosted-nodes.json`). */
|
|
81
|
+
store: HostedNodeStore;
|
|
82
|
+
/** The NAT layer, for the node's public addresses and reachability verdict. */
|
|
83
|
+
addresses: HostedNodeAddressSource;
|
|
84
|
+
/** Clock override for tests. */
|
|
85
|
+
now?: () => Date;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* HostedNodeService — the host's one action, "Join a cadre", and what follows from it.
|
|
89
|
+
*
|
|
90
|
+
* `join` starts a child waiting to be claimed, or one that redeems a cadre invitation;
|
|
91
|
+
* `claimDetails` reads a waiting node's addresses and builds the QR payload; the status
|
|
92
|
+
* watcher ({@link startWatching}) notices the claim or the redemption's outcome, and the
|
|
93
|
+
* node's liveness; `respawn` brings a dead child back as the same node with the same
|
|
94
|
+
* secret and ports; `retry` starts an invitation node again after no member could be
|
|
95
|
+
* reached; `remove` and `reset` end it. The host contributes capacity only: it never
|
|
96
|
+
* holds an owner key.
|
|
97
|
+
*
|
|
98
|
+
* **An ending that lands mid-operation wins.** Spawning a child takes seconds, and
|
|
99
|
+
* a `remove` can land inside that window. `hosted-nodes.json` is written a whole row
|
|
100
|
+
* at a time, so every long operation re-reads the record after its wait, decides
|
|
101
|
+
* against what is actually stored, and merges forward only the fields it itself
|
|
102
|
+
* produced — never the entry-time copy.
|
|
103
|
+
*
|
|
104
|
+
* **Respawns run one at a time** ({@link serializeRespawns}): the supervisor's passes and
|
|
105
|
+
* restarts and `retry` share one queue, since two overlapping respawns of one id both spawn
|
|
106
|
+
* a child and the second drops the first's orchestrator handle.
|
|
107
|
+
*/
|
|
108
|
+
export declare class HostedNodeService {
|
|
109
|
+
private readonly orchestrator;
|
|
110
|
+
private readonly store;
|
|
111
|
+
private readonly addresses;
|
|
112
|
+
private readonly now;
|
|
113
|
+
private readonly listeners;
|
|
114
|
+
private readonly watcher;
|
|
115
|
+
/** Serialization tail for `join`, so two joins at once cannot interleave their store writes. */
|
|
116
|
+
private joinTail;
|
|
117
|
+
/** Serialization tail for every respawn — see {@link serializeRespawns}. */
|
|
118
|
+
private respawnTail;
|
|
119
|
+
constructor(opts: HostedNodeServiceOptions);
|
|
120
|
+
/** Register a change listener (the server publishes each change as an SSE event). Returns an unsubscribe fn. */
|
|
121
|
+
onChange(listener: HostedNodeChangeListener): () => void;
|
|
122
|
+
/**
|
|
123
|
+
* Report a change to the listeners. Public for the supervisor's give-up, which
|
|
124
|
+
* writes the record itself; a listener that throws is logged, never propagated.
|
|
125
|
+
*/
|
|
126
|
+
emit(change: HostedNodeChange): void;
|
|
127
|
+
/** Start following every node's `/status` (claims and liveness). Idempotent. */
|
|
128
|
+
startWatching(): void;
|
|
129
|
+
stopWatching(): void;
|
|
130
|
+
/** One status pass now, awaited — for tests and callers that need the result before going on. */
|
|
131
|
+
pollStatuses(): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Start a node: write the record, spawn the child, and record its handles. Without an
|
|
134
|
+
* invitation the node waits to be claimed — a fresh claim secret, the placeholder
|
|
135
|
+
* party, `unclaimed`. With one it redeems the invitation — the invitation's party,
|
|
136
|
+
* `joining` — and an invitation that does not decode is `invalid_request` before
|
|
137
|
+
* anything is written. On any orchestrator failure the reserved resources are
|
|
138
|
+
* reclaimed and the record is marked `error`. Resolves once the child is spawned; its
|
|
139
|
+
* `/status` may take a few seconds more, which `claimDetails` reports as
|
|
140
|
+
* `node_unavailable`.
|
|
141
|
+
*/
|
|
142
|
+
join(opts?: JoinOptions): Promise<HostedNodeView>;
|
|
143
|
+
private joinLocked;
|
|
144
|
+
/**
|
|
145
|
+
* Mark a still-`spawning` record as failed — and ONLY such a record. A row that
|
|
146
|
+
* was removed while the spawn was in flight must not be recreated.
|
|
147
|
+
*
|
|
148
|
+
* Best-effort (mirroring {@link storeRespawnAttempt}): we are already unwinding a
|
|
149
|
+
* spawn failure, and a store error here must not mask the error being reported
|
|
150
|
+
* to the caller.
|
|
151
|
+
*/
|
|
152
|
+
private markSpawnFailed;
|
|
153
|
+
/**
|
|
154
|
+
* What the QR code carries for a node waiting to be claimed: its peer id, the
|
|
155
|
+
* addresses a phone may dial it at, and its claim secret — the only place the
|
|
156
|
+
* secret ever leaves the host. `409 invalid_state` unless the node is `unclaimed`;
|
|
157
|
+
* `503 node_unavailable` until the child's `/status` answers (callers poll).
|
|
158
|
+
*/
|
|
159
|
+
claimDetails(id: string): Promise<ClaimDetails>;
|
|
160
|
+
/** Cache the peer id on the record, once. Best-effort: the claim details do not depend on it. */
|
|
161
|
+
private recordPeerId;
|
|
162
|
+
/**
|
|
163
|
+
* Re-spawn a node that is no longer running, replaying the record's party and
|
|
164
|
+
* claim secret, or its invitation while it is still `joining`. The node keeps its
|
|
165
|
+
* workdir — and with it its identity key and node-local stores — and comes back on
|
|
166
|
+
* the same ports, so it is the *same* peer at the same addresses: a QR code already
|
|
167
|
+
* shown stays valid, and a joined node's cadre finds it where it was. Keeping the
|
|
168
|
+
* secret after the claim is deliberate: `cadre-cli` honours `claim.json` and, with the
|
|
169
|
+
* secret still set, answers a rival `already-claimed`. A joined invitation node comes
|
|
170
|
+
* back without its invitation ({@link spawnRequestFor} says why).
|
|
171
|
+
*
|
|
172
|
+
* **Status is deliberately unchanged.** A `joined` node stays `joined`; an
|
|
173
|
+
* `unclaimed` one keeps waiting, a `joining` one redeems again. `connected` is
|
|
174
|
+
* dropped until the watcher reads the new child.
|
|
175
|
+
*
|
|
176
|
+
* **An ending that lands mid-spawn wins.** The record is re-read after the
|
|
177
|
+
* orchestrator returns; if it was removed (or went `error`) while the child was
|
|
178
|
+
* starting, that write stands and the new child is abandoned — see
|
|
179
|
+
* {@link abandonRespawn}. An abandoned respawn is a skip, not a failure, so a
|
|
180
|
+
* sweep over the store keeps going. Orchestrator failures DO throw; the caller
|
|
181
|
+
* owns backoff/give-up.
|
|
182
|
+
*
|
|
183
|
+
* Not serialized itself: callers run it on {@link serializeRespawns}. Two overlapping
|
|
184
|
+
* calls for the same id both spawn a child, and the second spawn drops the first's
|
|
185
|
+
* orchestrator handle — leaving an unmanaged process.
|
|
186
|
+
*/
|
|
187
|
+
respawn(id: string, opts?: RespawnOptions): Promise<RespawnResult>;
|
|
188
|
+
/**
|
|
189
|
+
* Start an invitation node again after no member of its cadre could be reached: the
|
|
190
|
+
* `error` record goes back to `joining` and is respawned with its invitation, without
|
|
191
|
+
* spending a respawn attempt. On the respawn queue, so it never overlaps the
|
|
192
|
+
* supervisor's handling of the same node. `409 invalid_state` unless the node is `error`
|
|
193
|
+
* with `retryable` — a refused invitation stays refused, and the supervisor's give-up and
|
|
194
|
+
* the other failures are a reset's.
|
|
195
|
+
*/
|
|
196
|
+
retry(id: string): Promise<HostedNodeView>;
|
|
197
|
+
private retryLocked;
|
|
198
|
+
/**
|
|
199
|
+
* Give up on a respawn whose record ended while the child was starting: stop
|
|
200
|
+
* the new child and, unless the record went `error`, reclaim it.
|
|
201
|
+
*
|
|
202
|
+
* Reclaim, not merely stop. The removal's own cleanup already ran and found
|
|
203
|
+
* nothing: `HostProcessOrchestrator.createContainer` calls `dropStaleHandle`
|
|
204
|
+
* *while* spawning, so by the time the concurrent `remove` reached its
|
|
205
|
+
* stop/reclaim the old handle was gone and both calls were swallowed as
|
|
206
|
+
* best-effort no-ops. The new handle is now the only thing holding that
|
|
207
|
+
* spawn's ports and workdir, so stopping alone would leak both.
|
|
208
|
+
*
|
|
209
|
+
* `error` is the deliberate exception: `removeContainer` deletes the workdir,
|
|
210
|
+
* and `HostedNodeSupervisor.giveUp` keeps it on purpose so the identity key the
|
|
211
|
+
* node's cadre approved survives. Since both spawns share one workdir
|
|
212
|
+
* (`<rootDir>/<containerId>`), reclaiming here would delete exactly what
|
|
213
|
+
* `giveUp` meant to keep.
|
|
214
|
+
*
|
|
215
|
+
* The `error` record is then pointed at the new child, the one that exists, so a
|
|
216
|
+
* later `remove` reclaims its ports and workdir. `giveUp` runs on the respawn queue
|
|
217
|
+
* and cannot overlap a respawn; the watcher's write of a failed invitation can, when
|
|
218
|
+
* a poll of the old child answers while the new one is starting.
|
|
219
|
+
*/
|
|
220
|
+
private abandonRespawn;
|
|
221
|
+
/**
|
|
222
|
+
* Remove a hosted node: delete the row FIRST, then stop and reclaim the child
|
|
223
|
+
* (its workdir, identity key and data go with it). The node's cadre keeps the
|
|
224
|
+
* node's row until its owner removes it there.
|
|
225
|
+
*
|
|
226
|
+
* Order matters: stopping the child fires the orchestrator's `onStateChange`,
|
|
227
|
+
* and the supervisor's exit-triggered pass must find no record — otherwise it
|
|
228
|
+
* observes "node gone, record still `joined`" and brings back a node the admin
|
|
229
|
+
* just removed. The tradeoff: a crash between the delete and the stop leaves a
|
|
230
|
+
* running child no record names, which `remove` on its id still reclaims (below).
|
|
231
|
+
*
|
|
232
|
+
* A handle no record names — `hosted-nodes.json` lost, or a handle an older
|
|
233
|
+
* build persisted — is removed the same way, so `/api/nodes` cannot list a node
|
|
234
|
+
* for good that nothing can end.
|
|
235
|
+
*/
|
|
236
|
+
remove(id: string): Promise<void>;
|
|
237
|
+
private removeOrphanHandle;
|
|
238
|
+
/**
|
|
239
|
+
* Remove a node and start a fresh one waiting to be claimed, with a new secret and
|
|
240
|
+
* identity. For the case where someone else photographed the QR code and claimed the
|
|
241
|
+
* node first, and for an `error` node the admin wants to try again. An invitation node
|
|
242
|
+
* is replaced by one waiting to be claimed too: its invitation may be spent, and the
|
|
243
|
+
* admin can join with a fresh one instead.
|
|
244
|
+
*/
|
|
245
|
+
reset(id: string): Promise<HostedNodeView>;
|
|
246
|
+
/**
|
|
247
|
+
* Mark records stuck in `spawning` past `ttlMs` as `error` and reclaim whatever
|
|
248
|
+
* the orchestrator can still find for them — the record is written before the
|
|
249
|
+
* child spawn starts, so a host crash/kill anywhere in that window (including
|
|
250
|
+
* mid-spawn) leaves nothing to advance it across a restart. Run once at
|
|
251
|
+
* startup and on a periodic sweep. Age is measured from `updatedAt`, which for
|
|
252
|
+
* a genuinely-stuck row equals `createdAt` — `joinLocked` never touches a
|
|
253
|
+
* `spawning` record again except to advance it out of this predicate.
|
|
254
|
+
* Best-effort per record: a failed reap is logged and the sweep continues.
|
|
255
|
+
* Returns the reaped ids.
|
|
256
|
+
*/
|
|
257
|
+
reapStuckSpawning(ttlMs?: number): Promise<string[]>;
|
|
258
|
+
/**
|
|
259
|
+
* Terminalize a stuck-`spawning` record as `error` and reclaim any child the
|
|
260
|
+
* orchestrator can still find for it. Status is written FIRST (same ordering
|
|
261
|
+
* rule as {@link remove}): reclaiming fires `onStateChange`, and anything
|
|
262
|
+
* listening must already see a terminal record.
|
|
263
|
+
*
|
|
264
|
+
* NOTE: the stop only reaches a child the orchestrator has a handle for.
|
|
265
|
+
* `HostProcessOrchestrator.launchChild` spawns the process and persists the
|
|
266
|
+
* handle in the same synchronous step, so the only child this misses is one
|
|
267
|
+
* whose host died between the OS spawn and that write — an orphan *process*
|
|
268
|
+
* this reap terminalizes the record for but cannot kill (its ports stay held
|
|
269
|
+
* until a reboot). Its working directory is reclaimed by container name when
|
|
270
|
+
* no handle resolves, which also covers the far commoner case of a host that
|
|
271
|
+
* died before any child existed at all.
|
|
272
|
+
*/
|
|
273
|
+
private reclaimStuckSpawning;
|
|
274
|
+
/** One node (secret stripped), or undefined when unknown. */
|
|
275
|
+
get(id: string): HostedNodeView | undefined;
|
|
276
|
+
/** Every node (secret stripped). */
|
|
277
|
+
list(): HostedNodeView[];
|
|
278
|
+
private requireNode;
|
|
279
|
+
/** Best-effort stop; logs but never throws (cleanup path). */
|
|
280
|
+
private safeStop;
|
|
281
|
+
/**
|
|
282
|
+
* Persist a failed respawn's attempt counters (none for an uncounted restart) —
|
|
283
|
+
* plus, when the spawn itself succeeded and it was the record write that failed,
|
|
284
|
+
* the new child's handles. Merged onto whatever is on disk now, never written
|
|
285
|
+
* wholesale: the caller's copy predates the orchestrator round-trip.
|
|
286
|
+
*
|
|
287
|
+
* `status` and `updatedAt` are deliberately untouched — the attempt did not succeed.
|
|
288
|
+
*
|
|
289
|
+
* Recording `spawned` is what keeps the record pointing at the child that
|
|
290
|
+
* actually exists: `createContainer` already dropped the handle the record
|
|
291
|
+
* previously named, so leaving the old `dockerId` there would send every later
|
|
292
|
+
* stop/reclaim at an id the orchestrator cannot resolve — stranding the node's
|
|
293
|
+
* workdir on disk forever.
|
|
294
|
+
*
|
|
295
|
+
* Best-effort: we are already unwinding a respawn failure, and a store error
|
|
296
|
+
* here must not mask it. Losing the write costs the caller one extra attempt
|
|
297
|
+
* before backoff; the supervisor's next pass re-reads the record, finds the
|
|
298
|
+
* named child not running, and respawns again — which drops whatever handle is
|
|
299
|
+
* current, so the ports self-heal either way.
|
|
300
|
+
*/
|
|
301
|
+
private storeRespawnAttempt;
|
|
302
|
+
/** Best-effort remove; logs but never throws (cleanup path). */
|
|
303
|
+
private safeReclaim;
|
|
304
|
+
/**
|
|
305
|
+
* Best-effort removal of the working directory of a node whose spawn never
|
|
306
|
+
* produced a `dockerId`; logs but never throws (cleanup path). A no-op on an
|
|
307
|
+
* orchestrator without the capability, and on one that refuses because a live
|
|
308
|
+
* handle still owns the directory.
|
|
309
|
+
*/
|
|
310
|
+
private safeReclaimWorkdir;
|
|
311
|
+
/** Run `fn` after any in-flight join. */
|
|
312
|
+
private serializeJoin;
|
|
313
|
+
/**
|
|
314
|
+
* Run `fn` after every respawn already queued. Every caller of {@link respawn} goes
|
|
315
|
+
* through here: the supervisor's reconcile passes and restarts, and {@link retry}.
|
|
316
|
+
*/
|
|
317
|
+
serializeRespawns<T>(fn: () => Promise<T>): Promise<T>;
|
|
318
|
+
}
|