@serfab/cadre-host 1.14.0 → 1.15.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 +19 -4
- package/dist/bin/host.js +32 -20
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/nat-output.d.ts +5 -0
- package/dist/bin/nat-output.js +12 -1
- package/dist/bin/nat-output.js.map +1 -1
- package/dist/bin/ui-access.d.ts +22 -0
- package/dist/bin/ui-access.js +44 -0
- package/dist/bin/ui-access.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +25 -3
- package/dist/hosted/claim-details.js +31 -37
- package/dist/hosted/claim-details.js.map +1 -1
- package/dist/hosted/hosted-node-service.d.ts +7 -0
- package/dist/hosted/hosted-node-service.js +16 -4
- package/dist/hosted/hosted-node-service.js.map +1 -1
- package/dist/hosted/hosted-node-watcher.d.ts +16 -4
- package/dist/hosted/hosted-node-watcher.js +45 -9
- package/dist/hosted/hosted-node-watcher.js.map +1 -1
- package/dist/hosted/node-status.d.ts +19 -3
- package/dist/hosted/node-status.js +21 -5
- package/dist/hosted/node-status.js.map +1 -1
- package/dist/hosted/types.d.ts +12 -5
- package/dist/hosted/types.js.map +1 -1
- package/dist/installer/config.d.ts +23 -0
- package/dist/installer/config.js +46 -0
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +2 -0
- package/dist/installer/index.js +7 -16
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/wizard.d.ts +12 -2
- package/dist/installer/wizard.js +15 -7
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/index.d.ts +2 -1
- package/dist/nat/index.js +1 -0
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +22 -5
- package/dist/nat/nat-service.js +75 -26
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.js +11 -0
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/network-mode.d.ts +19 -0
- package/dist/nat/network-mode.js +32 -0
- package/dist/nat/network-mode.js.map +1 -0
- package/dist/nat/reachability.d.ts +5 -4
- package/dist/nat/reachability.js +19 -6
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +26 -6
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +3 -3
- package/dist/orchestrator/host-process-orchestrator.js +6 -15
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +4 -0
- package/dist/package-version.js.map +1 -0
- package/dist/server/routes/settings.d.ts +1 -0
- package/dist/server/routes/settings.js +15 -1
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.js +2 -32
- package/dist/server/routes/status.js.map +1 -1
- package/dist/ui/assets/index-BHd8CWZA.css +1 -0
- package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
- package/dist/ui/assets/{index-4FWuU8ye.js.map → index-Ccr2GjPT.js.map} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +4 -5
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/orchestrator-claim-secret.test.ts +1 -2
- package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
- package/src/__tests__/orchestrator-handle-contract.ts +1 -1
- package/src/__tests__/orchestrator-node-identity.test.ts +0 -5
- package/src/__tests__/orchestrator-push.test.ts +3 -3
- package/src/__tests__/orchestrator.test.ts +3 -5
- package/src/__tests__/ui-access.test.ts +30 -0
- package/src/bin/host.ts +30 -20
- package/src/bin/nat-output.ts +12 -1
- package/src/bin/ui-access.ts +50 -0
- package/src/hosted/__tests__/fake-orchestrator.test.ts +0 -1
- package/src/hosted/__tests__/fake-orchestrator.ts +263 -265
- package/src/hosted/__tests__/hosted-node-service.test.ts +708 -609
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -474
- package/src/hosted/claim-details.ts +44 -37
- package/src/hosted/hosted-node-service.ts +23 -4
- package/src/hosted/hosted-node-watcher.ts +288 -250
- package/src/hosted/node-status.ts +31 -5
- package/src/hosted/types.ts +160 -153
- package/src/installer/__tests__/installer.smoke.test.ts +184 -182
- package/src/installer/__tests__/wizard.test.ts +76 -56
- package/src/installer/config.ts +66 -0
- package/src/installer/index.ts +252 -259
- package/src/installer/wizard.ts +101 -87
- package/src/nat/__tests__/nat-service.test.ts +608 -540
- package/src/nat/__tests__/nat-store.test.ts +130 -127
- package/src/nat/__tests__/network-mode.test.ts +38 -0
- package/src/nat/index.ts +91 -87
- package/src/nat/nat-service.ts +79 -26
- package/src/nat/nat-store.ts +249 -237
- package/src/nat/network-mode.ts +36 -0
- package/src/nat/reachability.ts +155 -143
- package/src/nat/types.ts +232 -209
- package/src/orchestrator/host-process-orchestrator.ts +1089 -1098
- package/src/package-version.ts +4 -0
- package/src/server/__tests__/fakes.ts +41 -40
- package/src/server/__tests__/hosted-nodes-route.test.ts +161 -160
- package/src/server/__tests__/settings-route.test.ts +31 -0
- package/src/server/routes/settings.ts +128 -115
- package/src/server/routes/status.ts +72 -102
- package/dist/ui/assets/index-4FWuU8ye.js +0 -19
- package/dist/ui/assets/index-Di9Q4d01.css +0 -1
package/src/hosted/types.ts
CHANGED
|
@@ -1,153 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hosted-node types for cadre-host.
|
|
3
|
-
*
|
|
4
|
-
* A **hosted node** is a child process the host runs for some cadre. It gets into its
|
|
5
|
-
* cadre one of two ways: the host starts it waiting to be claimed and shows its addresses
|
|
6
|
-
* and claim secret as a QR code, which the owner's phone scans to claim the node
|
|
7
|
-
* (`CadreNode.claimNode`); or the host starts it with a cadre invitation the owner's app
|
|
8
|
-
* minted, which the node redeems at a member of the cadre. Either way the host holds no
|
|
9
|
-
* owner key and is not an owner of any cadre its nodes serve.
|
|
10
|
-
*
|
|
11
|
-
* Records live in `<dataDir>/hosted-nodes.json` (`HostedNodeStore`). A record's id is also
|
|
12
|
-
* its orchestrator container id and the name of its working directory.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Lifecycle of one hosted node:
|
|
17
|
-
* spawning → the record is written, the child not yet spawned.
|
|
18
|
-
* unclaimed → (claim) the child is up with a claim secret, waiting for a phone to claim it.
|
|
19
|
-
* joining → (invitation) the child is up, its redemption of the invitation not yet settled.
|
|
20
|
-
* joined → in its cadre: claimed, or the invitation accepted by a member.
|
|
21
|
-
* error → the supervisor gave up on it, the stuck-`spawning` reap found it,
|
|
22
|
-
* invitation was refused or no member could be reached (`retryable` says which)
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
export type
|
|
1
|
+
/**
|
|
2
|
+
* Hosted-node types for cadre-host.
|
|
3
|
+
*
|
|
4
|
+
* A **hosted node** is a child process the host runs for some cadre. It gets into its
|
|
5
|
+
* cadre one of two ways: the host starts it waiting to be claimed and shows its addresses
|
|
6
|
+
* and claim secret as a QR code, which the owner's phone scans to claim the node
|
|
7
|
+
* (`CadreNode.claimNode`); or the host starts it with a cadre invitation the owner's app
|
|
8
|
+
* minted, which the node redeems at a member of the cadre. Either way the host holds no
|
|
9
|
+
* owner key and is not an owner of any cadre its nodes serve.
|
|
10
|
+
*
|
|
11
|
+
* Records live in `<dataDir>/hosted-nodes.json` (`HostedNodeStore`). A record's id is also
|
|
12
|
+
* its orchestrator container id and the name of its working directory.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle of one hosted node:
|
|
17
|
+
* spawning → the record is written, the child not yet spawned.
|
|
18
|
+
* unclaimed → (claim) the child is up with a claim secret, waiting for a phone to claim it.
|
|
19
|
+
* joining → (invitation) the child is up, its redemption of the invitation not yet settled.
|
|
20
|
+
* joined → in its cadre: claimed, or the invitation accepted by a member.
|
|
21
|
+
* error → the supervisor gave up on it, the stuck-`spawning` reap found it, the
|
|
22
|
+
* invitation was refused or no member could be reached (`retryable` says which),
|
|
23
|
+
* or an owner removed it from its cadre (`removedByOwner`).
|
|
24
|
+
*
|
|
25
|
+
* Removal deletes the row; there is no terminal "removed" status, and a node an owner removed
|
|
26
|
+
* from its cadre is, to the host, a node that failed and waits for the admin. An `error`
|
|
27
|
+
* record keeps its working directory (its identity key) until it is removed or reset.
|
|
28
|
+
*/
|
|
29
|
+
export type HostedNodeStatus = 'spawning' | 'unclaimed' | 'joining' | 'joined' | 'error';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The node was started with a claim secret and waits for a phone to present it.
|
|
33
|
+
*/
|
|
34
|
+
export interface HostedNodeClaimJoin {
|
|
35
|
+
kind: 'claim';
|
|
36
|
+
/** The node's one-time claim secret, base64url. Only `claimDetails` ever returns it. */
|
|
37
|
+
secret: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The node was started with a cadre invitation and redeems it at a member of the cadre.
|
|
42
|
+
* Kept for a respawn while `joining`: the child redeems again, and a member answers a node
|
|
43
|
+
* that already got in as accepted.
|
|
44
|
+
*/
|
|
45
|
+
export interface HostedNodeInvitationJoin {
|
|
46
|
+
kind: 'invitation';
|
|
47
|
+
/**
|
|
48
|
+
* The bundle as the owner's app encoded it (`encodeCadreInvitation`). It carries the
|
|
49
|
+
* invitation's private key, so it is redacted like the claim secret.
|
|
50
|
+
*
|
|
51
|
+
* NOTE: kept on disk after the node joins, though nothing reads it then (a joined node is
|
|
52
|
+
* respawned without it). An invitation with uses left admits whoever reads it; if
|
|
53
|
+
* multi-use invitations get pasted into hosts, drop it at the watcher's `joined` write.
|
|
54
|
+
*/
|
|
55
|
+
encoded: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** How a node gets into its cadre. */
|
|
59
|
+
export type HostedNodeJoin = HostedNodeClaimJoin | HostedNodeInvitationJoin;
|
|
60
|
+
|
|
61
|
+
/** One hosted node, as persisted in `hosted-nodes.json`. */
|
|
62
|
+
export interface HostedNode {
|
|
63
|
+
/** `hn_<base64url of 12 random bytes>` — also the orchestrator container id and workdir name. */
|
|
64
|
+
id: string;
|
|
65
|
+
join: HostedNodeJoin;
|
|
66
|
+
/**
|
|
67
|
+
* A claim node: `unclaimed` (the placeholder party the child's config names) until the
|
|
68
|
+
* claim reports the party; the claimant's party after. An invitation node: the
|
|
69
|
+
* invitation's party from the start.
|
|
70
|
+
*/
|
|
71
|
+
partyId: string;
|
|
72
|
+
/** Hosted nodes run the `storage` profile: they participate in strands and are dialable. */
|
|
73
|
+
profile: 'storage';
|
|
74
|
+
status: HostedNodeStatus;
|
|
75
|
+
/** Orchestrator handle (opaque `pid:token`), set once the child is spawned. */
|
|
76
|
+
dockerId?: string;
|
|
77
|
+
/** The child's loopback `/status` URL, from the spawn result. */
|
|
78
|
+
statusEndpoint?: string;
|
|
79
|
+
/** Read once from `/status`; the claim details need it before the claim. */
|
|
80
|
+
peerId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Set when the node joins: `/status.node.claimedBy` for a claim, the invitation's issuer
|
|
83
|
+
* key for an invitation. The UI shows its first 8 characters.
|
|
84
|
+
*/
|
|
85
|
+
ownerKey?: string;
|
|
86
|
+
/** An invitation node, once joined: the member that admitted it, when its address named one. */
|
|
87
|
+
memberPeerId?: string;
|
|
88
|
+
/** From the last status poll: the node holds at least one control connection. */
|
|
89
|
+
connected?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Respawn bookkeeping, owned by the supervisor's backoff. Absent until the first
|
|
92
|
+
* respawn attempt. Not secret — it rides along in `HostedNodeView`.
|
|
93
|
+
*/
|
|
94
|
+
respawn?: { attempts: number; lastAttemptAt: string };
|
|
95
|
+
createdAt: string;
|
|
96
|
+
updatedAt: string;
|
|
97
|
+
/** Failure detail when `status === 'error'`. */
|
|
98
|
+
error?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Set with `error` when an invitation failed: whether Retry may start the node again with
|
|
101
|
+
* the same invitation (no member could be reached), or the invitation itself was refused.
|
|
102
|
+
* False on a node an owner removed; absent on every other `error`.
|
|
103
|
+
*/
|
|
104
|
+
retryable?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Set with `error` when the node reported that an owner removed it from its cadre, so the
|
|
107
|
+
* UI words it as a removal rather than a failure to join or to stay up.
|
|
108
|
+
*/
|
|
109
|
+
removedByOwner?: true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** `join` without its secret: what `list`, `get`, the routes and the events carry. */
|
|
113
|
+
export type HostedNodeJoinView = { kind: HostedNodeJoin['kind'] };
|
|
114
|
+
|
|
115
|
+
/** The wire shape of a hosted node: the record with the claim secret or the invitation stripped. */
|
|
116
|
+
export interface HostedNodeView extends Omit<HostedNode, 'join'> {
|
|
117
|
+
join: HostedNodeJoinView;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** On-disk shape of `hosted-nodes.json`. Keyed by node id. */
|
|
121
|
+
export interface HostedNodeFile {
|
|
122
|
+
version: 1;
|
|
123
|
+
nodes: Record<string, Omit<HostedNode, 'id'>>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Error codes thrown by the hosted-node service, mapped to HTTP status by `server/error-handler.ts`. */
|
|
127
|
+
export type HostedNodeErrorCode =
|
|
128
|
+
| 'not_found' // 404 — no such hosted node
|
|
129
|
+
| 'invalid_state' // 409 — the operation does not apply to the node's current status
|
|
130
|
+
| 'invalid_request' // 400 — malformed input
|
|
131
|
+
| 'node_unavailable' // 503 — the child's /status does not answer yet
|
|
132
|
+
| 'orchestrator_error' // 500 — spawn / stop failure
|
|
133
|
+
| 'storage_error'; // 500 — hosted-nodes.json read/write failure
|
|
134
|
+
|
|
135
|
+
/** Typed hosted-node error carrying a stable `code` for HTTP mapping. */
|
|
136
|
+
export class HostedNodeError extends Error {
|
|
137
|
+
readonly code: HostedNodeErrorCode;
|
|
138
|
+
|
|
139
|
+
constructor(code: HostedNodeErrorCode, message: string) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.name = 'HostedNodeError';
|
|
142
|
+
this.code = code;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* What changed about a hosted node: `added` (a join started the node), `claimed` (the
|
|
148
|
+
* watcher saw the claim), `joined` (the watcher saw a member accept the invitation),
|
|
149
|
+
* `removed`, or `changed` (anything else: a respawn, a liveness change, a refused
|
|
150
|
+
* invitation, a retry, a give-up). The server publishes each as the `hosted-nodes-changed`
|
|
151
|
+
* SSE event.
|
|
152
|
+
*/
|
|
153
|
+
export type HostedNodeChangeKind = 'added' | 'claimed' | 'joined' | 'removed' | 'changed';
|
|
154
|
+
|
|
155
|
+
export interface HostedNodeChange {
|
|
156
|
+
kind: HostedNodeChangeKind;
|
|
157
|
+
id: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type HostedNodeChangeListener = (change: HostedNodeChange) => void;
|