agent-relay 12.2.7 → 12.3.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 +66 -0
- package/dist/cli/commands/core.d.ts +13 -1
- package/dist/cli/commands/core.d.ts.map +1 -1
- package/dist/cli/commands/core.js +3 -1
- package/dist/cli/commands/core.js.map +1 -1
- package/dist/cli/commands/node.d.ts +6 -0
- package/dist/cli/commands/node.d.ts.map +1 -1
- package/dist/cli/commands/node.js +72 -0
- package/dist/cli/commands/node.js.map +1 -1
- package/dist/cli/lib/broker-lifecycle.d.ts +21 -1
- package/dist/cli/lib/broker-lifecycle.d.ts.map +1 -1
- package/dist/cli/lib/broker-lifecycle.js +354 -4
- package/dist/cli/lib/broker-lifecycle.js.map +1 -1
- package/dist/cli/lib/client-factory.d.ts +4 -0
- package/dist/cli/lib/client-factory.d.ts.map +1 -1
- package/dist/cli/lib/client-factory.js +3 -1
- package/dist/cli/lib/client-factory.js.map +1 -1
- package/dist/cli/lib/node-claim.d.ts +460 -0
- package/dist/cli/lib/node-claim.d.ts.map +1 -0
- package/dist/cli/lib/node-claim.js +1338 -0
- package/dist/cli/lib/node-claim.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/package.json +10 -10
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-local record of which live broker serves an enrolled node id.
|
|
3
|
+
*
|
|
4
|
+
* The Fleet enrollment store (`~/.agentworkforce/relay/fleet-enrollments.json`)
|
|
5
|
+
* is machine-global and NOT scoped to a broker state dir, so two `node up`
|
|
6
|
+
* invocations in different projects — or with different `--state-dir` values —
|
|
7
|
+
* happily adopt the same node id. The second registration takes over that
|
|
8
|
+
* node's Cloud delivery socket and the first broker stops receiving messages
|
|
9
|
+
* with no error on either side. This claim is the machine-local mutual
|
|
10
|
+
* exclusion the enrollment store does not provide.
|
|
11
|
+
*
|
|
12
|
+
* Ownership moves through two phases. A supervising CLI first *reserves* the
|
|
13
|
+
* node id (`status: 'reserved'`, `pid` = the CLI's own) BEFORE it spawns
|
|
14
|
+
* anything, because the broker queues `node.register` during its own
|
|
15
|
+
* initialization — any claim written after the spawn is written after the
|
|
16
|
+
* delivery socket could already have moved. Once a verified broker process
|
|
17
|
+
* exists the reservation is *adopted* (`status: 'active'`, `pid` = the broker's)
|
|
18
|
+
* in place, so ownership is never dropped in between.
|
|
19
|
+
*
|
|
20
|
+
* Neither phase depends on a process surviving long enough to record anything:
|
|
21
|
+
* the reservation also opens a hold descriptor that the broker child inherits
|
|
22
|
+
* across `fork` ({@link nodeClaimHoldPath}), so a supervisor killed anywhere
|
|
23
|
+
* between the spawn and the broker's first write still leaves a claim the
|
|
24
|
+
* kernel answers for.
|
|
25
|
+
*/
|
|
26
|
+
export interface NodeClaim {
|
|
27
|
+
version: 1;
|
|
28
|
+
node_id: string;
|
|
29
|
+
/**
|
|
30
|
+
* PID that owns the node id: the supervising CLI while the claim is
|
|
31
|
+
* `reserved`, the broker process holding the node-control socket once it is
|
|
32
|
+
* `active`.
|
|
33
|
+
*/
|
|
34
|
+
pid: number;
|
|
35
|
+
/** Resolved broker state directory (`--state-dir`, or the project default). */
|
|
36
|
+
state_dir: string;
|
|
37
|
+
/** Broker HTTP API port, for pointing an operator at the live process. */
|
|
38
|
+
api_port?: number;
|
|
39
|
+
/** Registered broker/node name, when the start resolved one. */
|
|
40
|
+
broker_name?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Absolute path of the executable this start resolved to run as its broker,
|
|
43
|
+
* and that file's `<device>:<inode>` at the moment it was recorded.
|
|
44
|
+
*
|
|
45
|
+
* This is what a later start compares a live process against when it has to
|
|
46
|
+
* decide whether that process is the broker holding the node id. The broker
|
|
47
|
+
* binary is operator-selectable (`AGENT_RELAY_BIN` / `BROKER_BINARY_PATH`)
|
|
48
|
+
* and a supported deployment may run it under any filename, so looking for
|
|
49
|
+
* `agent-relay` in a command line ruled exactly such a broker OUT — the
|
|
50
|
+
* "node id free" verdict that evicts a live broker's delivery socket. Both
|
|
51
|
+
* are best effort; see {@link classifyHolderProcess} for what their absence
|
|
52
|
+
* means.
|
|
53
|
+
*/
|
|
54
|
+
broker_binary?: string;
|
|
55
|
+
broker_executable?: string;
|
|
56
|
+
/**
|
|
57
|
+
* PID of the child this start handed the hold descriptor to, recorded by the
|
|
58
|
+
* supervising CLI in the same turn `spawn()` returned it and long before any
|
|
59
|
+
* handshake completes.
|
|
60
|
+
*
|
|
61
|
+
* The executable identity above is recorded BEFORE the spawn, so it names the
|
|
62
|
+
* file the start was going to run — which is not always the file the running
|
|
63
|
+
* process maps. A shebang launcher runs as its interpreter (`lsof -d txt`
|
|
64
|
+
* reports `/bin/sh`), and a launcher that `exec`s the real broker maps that
|
|
65
|
+
* binary instead; neither matches what the claim recorded, and the holder
|
|
66
|
+
* read as an unrelated process. A pid survives both transitions, because
|
|
67
|
+
* `execve` keeps it. Only ever used as POSITIVE evidence, and only for a
|
|
68
|
+
* process already proven to hold this generation's descriptor — where a
|
|
69
|
+
* recycled number cannot reach, since nothing but a Relay start passes that
|
|
70
|
+
* descriptor on. See {@link classifyHolderProcess}.
|
|
71
|
+
*/
|
|
72
|
+
broker_child_pid?: number;
|
|
73
|
+
/**
|
|
74
|
+
* `ps -o lstart` of `pid` at claim time. A PID alone cannot survive a reboot
|
|
75
|
+
* or wraparound: without this, an unrelated process that inherits the number
|
|
76
|
+
* would read as a live claim forever.
|
|
77
|
+
*/
|
|
78
|
+
process_started_at?: string;
|
|
79
|
+
/**
|
|
80
|
+
* PID of the CLI supervising the broker, recorded from the reservation
|
|
81
|
+
* onward. After adoption the claim names both, and either one being alive
|
|
82
|
+
* keeps it held: a supervisor that dies after its broker registered must not
|
|
83
|
+
* leave that broker unprotected, and a broker that dies before its supervisor
|
|
84
|
+
* has finished shutting down must not open the node id up while the socket is
|
|
85
|
+
* still being torn down.
|
|
86
|
+
*/
|
|
87
|
+
supervisor_pid?: number;
|
|
88
|
+
supervisor_started_at?: string;
|
|
89
|
+
/** `reserved` until a verified broker process owns the state dir. */
|
|
90
|
+
status?: 'reserved' | 'active';
|
|
91
|
+
claimed_at: string;
|
|
92
|
+
/**
|
|
93
|
+
* Sequence number encoded in this claim's filename. Ownership IS the
|
|
94
|
+
* successful exclusive creation of `<node>.<generation>.json`.
|
|
95
|
+
*
|
|
96
|
+
* A number is issued at most once for the life of the stem: releasing leaves
|
|
97
|
+
* a tombstone rather than removing the file, so `max(generation)` never
|
|
98
|
+
* decreases and no suspended start can wake up and re-create a number that
|
|
99
|
+
* has since been handed to somebody else. See {@link acquireNodeClaim} and
|
|
100
|
+
* {@link NodeClaimTombstone}.
|
|
101
|
+
*/
|
|
102
|
+
generation?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Unique per acquisition. A claim is only ever rewritten or removed by the
|
|
105
|
+
* acquisition that created it, and this is how that is proven.
|
|
106
|
+
*/
|
|
107
|
+
owner_token?: string;
|
|
108
|
+
}
|
|
109
|
+
/** Whether a node id is free to serve, held by a live broker, or left behind. */
|
|
110
|
+
export type NodeClaimState =
|
|
111
|
+
/** No claim file, or one that cannot be trusted to describe a live broker. */
|
|
112
|
+
{
|
|
113
|
+
state: 'unclaimed';
|
|
114
|
+
}
|
|
115
|
+
/** A live local broker serves this node id; adopting it would evict its socket. */
|
|
116
|
+
| {
|
|
117
|
+
state: 'held';
|
|
118
|
+
claim: NodeClaim;
|
|
119
|
+
reason: string;
|
|
120
|
+
}
|
|
121
|
+
/** A claim file survives a broker that no longer runs; safe to take over. */
|
|
122
|
+
| {
|
|
123
|
+
state: 'stale';
|
|
124
|
+
claim: NodeClaim;
|
|
125
|
+
reason: string;
|
|
126
|
+
};
|
|
127
|
+
export interface NodeClaimDependencies {
|
|
128
|
+
/** Process environment, for the `AGENT_RELAY_HOME` override. */
|
|
129
|
+
env?: NodeJS.ProcessEnv;
|
|
130
|
+
/** Signal sender used for liveness probes. Defaults to `process.kill`. */
|
|
131
|
+
killProcess?: (pid: number, signal?: NodeJS.Signals | number) => void;
|
|
132
|
+
/**
|
|
133
|
+
* Shell runner used to read a pid's birth time and command line. Every CLI
|
|
134
|
+
* command passes its `CoreDependencies.execCommand`; without one the pid-reuse
|
|
135
|
+
* check is skipped and a live pid simply reads as held. This module imports no
|
|
136
|
+
* child_process itself so a claim check can be made from commands that mock
|
|
137
|
+
* that module.
|
|
138
|
+
*/
|
|
139
|
+
execCommand?: (command: string) => Promise<{
|
|
140
|
+
stdout: string;
|
|
141
|
+
stderr: string;
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
export interface AcquireNodeClaimInput extends NodeClaimDependencies {
|
|
145
|
+
nodeId: string;
|
|
146
|
+
/** PID that will own the node id (the supervising CLI, for a reservation). */
|
|
147
|
+
pid: number;
|
|
148
|
+
stateDir: string;
|
|
149
|
+
apiPort?: number;
|
|
150
|
+
brokerName?: string;
|
|
151
|
+
/** Take the node over from a live broker instead of refusing. */
|
|
152
|
+
force?: boolean;
|
|
153
|
+
/** `reserved` (pre-spawn) or `active` (a verified broker owns the state dir). */
|
|
154
|
+
status?: 'reserved' | 'active';
|
|
155
|
+
/** Supervising CLI pid, when it differs from the owning `pid`. */
|
|
156
|
+
supervisorPid?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Path of the executable this start will run as the broker, recorded so a
|
|
159
|
+
* later start can recognise that process by executable identity rather than
|
|
160
|
+
* by its filename. See {@link NodeClaim.broker_binary}.
|
|
161
|
+
*/
|
|
162
|
+
brokerBinary?: string;
|
|
163
|
+
}
|
|
164
|
+
/** Thrown when a live local broker already serves the requested node id. */
|
|
165
|
+
export declare class NodeClaimConflictError extends Error {
|
|
166
|
+
readonly nodeId: string;
|
|
167
|
+
readonly claim: NodeClaim;
|
|
168
|
+
constructor(nodeId: string, claim: NodeClaim);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Thrown when the kernel-level ownership fence could not be established.
|
|
172
|
+
*
|
|
173
|
+
* The hold descriptor is what makes a claim survive its own supervisor, so a
|
|
174
|
+
* start that cannot open one cannot honour the guarantee it is claiming under.
|
|
175
|
+
* It refuses instead of spawning an unfenced broker.
|
|
176
|
+
*/
|
|
177
|
+
export declare class NodeClaimHoldError extends Error {
|
|
178
|
+
readonly nodeId: string;
|
|
179
|
+
readonly holdPath: string;
|
|
180
|
+
constructor(nodeId: string, holdPath: string, cause?: unknown);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Thrown when ownership could not be established because other starts kept
|
|
184
|
+
* winning the exclusive create.
|
|
185
|
+
*
|
|
186
|
+
* Each attempt is a handful of syscalls, so exhausting them means a pathological
|
|
187
|
+
* amount of contention on one node id. Ownership cannot be established in that
|
|
188
|
+
* state, and starting anyway is exactly the double registration this module
|
|
189
|
+
* exists to prevent.
|
|
190
|
+
*/
|
|
191
|
+
export declare class NodeClaimContentionError extends Error {
|
|
192
|
+
readonly nodeId: string;
|
|
193
|
+
readonly claimsDir: string;
|
|
194
|
+
constructor(nodeId: string, claimsDir: string, cause?: unknown);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The enrolled node id a start will register as, or `undefined` when it will
|
|
198
|
+
* mint its own identity.
|
|
199
|
+
*
|
|
200
|
+
* `RELAY_NODE_ID` is sent verbatim in `node.register` (`resolve_broker_node_id`,
|
|
201
|
+
* crates/broker/src/runtime/init.rs), so an explicit node id IS the identity
|
|
202
|
+
* that can evict another broker, and it is claimed on that basis alone.
|
|
203
|
+
*
|
|
204
|
+
* This deliberately does not try to predict whether the broker will manage to
|
|
205
|
+
* authenticate as it. An earlier version claimed the id only when a token was
|
|
206
|
+
* in the environment or already in the broker's on-disk cache, and that was a
|
|
207
|
+
* hole: `init.rs` also wires a workspace-key token minter, and
|
|
208
|
+
* `node_control.rs` mints and connects with no cached token at all, so a start
|
|
209
|
+
* carrying nothing but `RELAY_NODE_ID` and workspace credentials walked past a
|
|
210
|
+
* live claim and took the node's delivery socket — the incident this module
|
|
211
|
+
* exists to close. Enumerating the credential routes instead of the identity
|
|
212
|
+
* just moves the hole to the next route that gets added.
|
|
213
|
+
*
|
|
214
|
+
* The cost is a start that could not have registered at all (no token, no
|
|
215
|
+
* workspace key, nothing to mint with) being refused when some other broker
|
|
216
|
+
* genuinely holds the id. That is recoverable in one flag (`--force`) and the
|
|
217
|
+
* start it refuses was headed for local-only operation anyway, while the
|
|
218
|
+
* opposite error is a silent delivery outage. `--local-only` starts register
|
|
219
|
+
* nothing and are excluded by the callers, not here.
|
|
220
|
+
*/
|
|
221
|
+
export declare function enrolledNodeIdForClaim(env: NodeJS.ProcessEnv): string | undefined;
|
|
222
|
+
/** Directory holding the claim files for every node id served on this machine. */
|
|
223
|
+
export declare function nodeClaimsDir(env?: NodeJS.ProcessEnv): string;
|
|
224
|
+
/**
|
|
225
|
+
* Path of one generation of a node id's claim.
|
|
226
|
+
*
|
|
227
|
+
* Every write this module makes is the exclusive creation of a NEW generation,
|
|
228
|
+
* never an overwrite of an existing one — that is what makes takeover safe
|
|
229
|
+
* without a lock file (see {@link acquireNodeClaim}).
|
|
230
|
+
*/
|
|
231
|
+
export declare function nodeClaimPath(nodeId: string, env?: NodeJS.ProcessEnv, generation?: number): string;
|
|
232
|
+
/**
|
|
233
|
+
* Sibling of a claim generation whose OPEN DESCRIPTORS are the ownership
|
|
234
|
+
* evidence, held by the kernel rather than written by a process.
|
|
235
|
+
*
|
|
236
|
+
* `<stem>.<generation>.hold` is created and opened by the supervising CLI
|
|
237
|
+
* BEFORE it spawns a broker, and the descriptor is inherited by the child
|
|
238
|
+
* across `fork`. From the instant a broker child exists — long before it binds
|
|
239
|
+
* its API, writes `connection.json` or queues `node.register` — some live
|
|
240
|
+
* process holds this file open, and the kernel drops the last reference the
|
|
241
|
+
* moment both of them die. That is what closes the window a supervisor
|
|
242
|
+
* SIGKILLed between the spawn and the broker's first write used to leave: there
|
|
243
|
+
* is no interval in which a competing start can see "dead supervisor, nothing
|
|
244
|
+
* published" and conclude the node id is free while the orphan is on its way to
|
|
245
|
+
* registering.
|
|
246
|
+
*
|
|
247
|
+
* Not matched by {@link CLAIM_FILENAME_PATTERN} (which requires `.json`), so it
|
|
248
|
+
* never counts as a generation of its own.
|
|
249
|
+
*/
|
|
250
|
+
export declare function nodeClaimHoldPath(nodeId: string, env?: NodeJS.ProcessEnv, generation?: number): string;
|
|
251
|
+
/** Resolve a state dir to its canonical path so two spellings compare equal. */
|
|
252
|
+
export declare function normalizeClaimStateDir(stateDir: string): string;
|
|
253
|
+
/**
|
|
254
|
+
* The claim that currently owns `nodeId`, or `null`.
|
|
255
|
+
*
|
|
256
|
+
* A missing, unreadable, or malformed file reads as `null`: an unparseable
|
|
257
|
+
* claim is no evidence of a live broker, and treating it as one would brick
|
|
258
|
+
* every later `node up` for that node id.
|
|
259
|
+
*/
|
|
260
|
+
export declare function readNodeClaim(nodeId: string, env?: NodeJS.ProcessEnv): NodeClaim | null;
|
|
261
|
+
/** The current claim for every node id on this machine, newest first. */
|
|
262
|
+
export declare function listNodeClaims(env?: NodeJS.ProcessEnv): NodeClaim[];
|
|
263
|
+
/**
|
|
264
|
+
* Whether anything other than `selfPids` still holds this claim's generation
|
|
265
|
+
* fenced, for a caller deciding whether ownership may be dropped.
|
|
266
|
+
*
|
|
267
|
+
* A release has to consult this, not just the pids on the record. The record
|
|
268
|
+
* names a broker only once a start got far enough to capture one: a spawn that
|
|
269
|
+
* rejects before it returns a client — a handshake that never completed, a
|
|
270
|
+
* SIGTERM the child outlived — leaves a broker child that no pid anywhere names,
|
|
271
|
+
* and releasing on "no live pid recorded" tombstones the claim and unlinks the
|
|
272
|
+
* hold file out from under a process that is still fenced by it. The next start
|
|
273
|
+
* then reads the node id as free while that child can still register. The
|
|
274
|
+
* descriptor is the one piece of evidence that exists from the instant the child
|
|
275
|
+
* does, so it is what decides.
|
|
276
|
+
*
|
|
277
|
+
* Holders are filtered exactly as {@link inspectNodeClaim} filters them, so a
|
|
278
|
+
* release never frees a node id that another start would still read as held.
|
|
279
|
+
*/
|
|
280
|
+
export declare function inspectNodeClaimHold(claim: NodeClaim, deps?: NodeClaimDependencies, selfPids?: ReadonlySet<number>): Promise<{
|
|
281
|
+
held: boolean;
|
|
282
|
+
reason: string;
|
|
283
|
+
pids: number[];
|
|
284
|
+
}>;
|
|
285
|
+
/**
|
|
286
|
+
* Create and open this generation's hold file, handing the supervising CLI the
|
|
287
|
+
* descriptor it passes to the broker child.
|
|
288
|
+
*
|
|
289
|
+
* Created exclusively (`wx`): only the acquisition that won the generation ever
|
|
290
|
+
* creates it, so a concurrent start can never replace the inode a live child is
|
|
291
|
+
* holding. Opened BEFORE the spawn, because a fence established after `fork`
|
|
292
|
+
* would leave exactly the gap it exists to close.
|
|
293
|
+
*
|
|
294
|
+
* Failing to establish the hold is FATAL to the start, which is why this throws
|
|
295
|
+
* rather than reporting a missing fence. Without the descriptor, a supervisor
|
|
296
|
+
* killed between the fork and the broker's `connection.json` write leaves a
|
|
297
|
+
* claim with only dead pids and no evidence at all — so the next start reads
|
|
298
|
+
* the node id as free and evicts a broker that is about to register. Continuing
|
|
299
|
+
* would mean spawning a broker under a guarantee that is not actually in force,
|
|
300
|
+
* which is worse than not starting: the operator can see and fix a refusal.
|
|
301
|
+
*
|
|
302
|
+
* @throws NodeClaimHoldError when the hold could not be established. Any
|
|
303
|
+
* partially created file and descriptor are cleaned up first, so a retry (or a
|
|
304
|
+
* later start, on the next generation) is not blocked by this one's debris.
|
|
305
|
+
*/
|
|
306
|
+
export declare function openNodeClaimHold(claim: NodeClaim, env?: NodeJS.ProcessEnv): number;
|
|
307
|
+
/** Drop this process's reference to a hold file. Other holders keep it alive. */
|
|
308
|
+
export declare function closeNodeClaimHold(fd: number | undefined): void;
|
|
309
|
+
/**
|
|
310
|
+
* A live broker serving `stateDir`, discovered from the connection file the
|
|
311
|
+
* broker writes itself.
|
|
312
|
+
*
|
|
313
|
+
* This is the ownership evidence that survives the supervising CLI. The Rust
|
|
314
|
+
* broker writes `<state dir>/connection.json` (with its own pid) as soon as its
|
|
315
|
+
* API listener binds — BEFORE `connect_relay` and before `node.register` is
|
|
316
|
+
* queued (crates/broker/src/runtime/init.rs) — so a broker that was orphaned by
|
|
317
|
+
* a SIGKILLed supervisor before the CLI could record its pid is still
|
|
318
|
+
* discoverable by every other start on the machine. Without it, such an orphan
|
|
319
|
+
* reads as "nobody home" and the next `node up` evicts its delivery socket.
|
|
320
|
+
*/
|
|
321
|
+
export declare function findLiveStateDirBroker(stateDir: string, deps?: NodeClaimDependencies,
|
|
322
|
+
/** What the claim for this state dir knows about its broker executable. */
|
|
323
|
+
hint?: {
|
|
324
|
+
binary?: string;
|
|
325
|
+
object?: string;
|
|
326
|
+
}): Promise<{
|
|
327
|
+
pid: number;
|
|
328
|
+
startedAt?: string;
|
|
329
|
+
} | null>;
|
|
330
|
+
/**
|
|
331
|
+
* Classify a claim for `nodeId` — the read-only view used by preflight guards
|
|
332
|
+
* and diagnostics.
|
|
333
|
+
*
|
|
334
|
+
* A claim is retired only when every process it names is provably gone (the pid
|
|
335
|
+
* no longer exists, or its birth time no longer matches the one recorded) AND
|
|
336
|
+
* no live broker occupies its state dir. Everything else — including a `ps` we
|
|
337
|
+
* cannot run — reads as held, because refusing is recoverable (`--force`,
|
|
338
|
+
* `node down`) while a wrong "free" verdict silently cuts delivery to a live
|
|
339
|
+
* broker.
|
|
340
|
+
*/
|
|
341
|
+
export declare function inspectNodeClaim(nodeId: string, deps?: NodeClaimDependencies): Promise<NodeClaimState>;
|
|
342
|
+
/** Live claims on this machine, for locating a broker across state dirs. */
|
|
343
|
+
export declare function listHeldNodeClaims(deps?: NodeClaimDependencies): Promise<NodeClaim[]>;
|
|
344
|
+
/**
|
|
345
|
+
* Record ownership of `nodeId`, refusing if a live local broker already holds
|
|
346
|
+
* it.
|
|
347
|
+
*
|
|
348
|
+
* Ownership IS the exclusive creation of the next generation file. Generations
|
|
349
|
+
* only increase and each one is created exactly once — for the whole life of
|
|
350
|
+
* the stem, not just while a claim is live: {@link releaseNodeClaim} leaves a
|
|
351
|
+
* tombstone in place of the record it drops, so the highest number on disk
|
|
352
|
+
* never falls back. Taking a node id over therefore never involves deleting a
|
|
353
|
+
* file another process might have replaced in the meantime — the failure mode
|
|
354
|
+
* that makes "validate the holder, then remove its record, then write ours"
|
|
355
|
+
* unsafe no matter how the validation is fenced.
|
|
356
|
+
*
|
|
357
|
+
* Without that, a start suspended between the scan and the create could wake up
|
|
358
|
+
* after a complete release-and-reacquire cycle, re-create a number that had
|
|
359
|
+
* been reissued to a live successor, pass the higher-generation check because
|
|
360
|
+
* its own file was the highest again, and then prune that successor's claim
|
|
361
|
+
* from its own stale scan. Both would stay alive with one of them recorded —
|
|
362
|
+
* exactly the double registration this module exists to prevent.
|
|
363
|
+
*
|
|
364
|
+
* Two starts that both observe the same stale claim therefore cannot both win:
|
|
365
|
+
* one creates generation N+1, the other collides (`EEXIST`) and re-reads, now
|
|
366
|
+
* seeing the winner's live claim. A start that created its generation while a
|
|
367
|
+
* third one was creating a higher one loses the confirm step below, removes its
|
|
368
|
+
* own file and refuses. Exactly one — the highest generation NUMBER ever issued
|
|
369
|
+
* for the stem, live claim or spent tombstone — can own the node id, which is
|
|
370
|
+
* the invariant the post-create check below enforces.
|
|
371
|
+
*
|
|
372
|
+
* Callers reserve with `status: 'reserved'` and their own pid BEFORE spawning a
|
|
373
|
+
* broker, open the generation's hold descriptor with {@link openNodeClaimHold}
|
|
374
|
+
* so the child inherits it, and then hand ownership to the verified broker with
|
|
375
|
+
* {@link adoptNodeClaim}.
|
|
376
|
+
*
|
|
377
|
+
* @throws NodeClaimConflictError when a live local broker holds the node id and
|
|
378
|
+
* `force` was not requested.
|
|
379
|
+
* @throws NodeClaimContentionError when no generation could be won at all.
|
|
380
|
+
*/
|
|
381
|
+
export declare function acquireNodeClaim(input: AcquireNodeClaimInput): Promise<NodeClaim>;
|
|
382
|
+
/**
|
|
383
|
+
* Record the pid of the child this reservation just handed its hold descriptor
|
|
384
|
+
* to, so a later start can recognise that process no matter what it becomes.
|
|
385
|
+
*
|
|
386
|
+
* Deliberately synchronous, and called from the spawn itself rather than from
|
|
387
|
+
* the adoption that follows it: the whole point is to have the pid on disk
|
|
388
|
+
* before the child can `exec` into something the recorded executable no longer
|
|
389
|
+
* describes, and before this supervisor can be killed. It is a plain write of
|
|
390
|
+
* OUR OWN generation with no `await` between the read and the write, so it
|
|
391
|
+
* cannot interleave with anything.
|
|
392
|
+
*
|
|
393
|
+
* A failure is never fatal. This is additional positive evidence layered on top
|
|
394
|
+
* of a fence that is already in place — the descriptor was inherited at the
|
|
395
|
+
* spawn — so losing it costs the launcher/exec case, not the guarantee. The
|
|
396
|
+
* caller keeps the returned claim, which is unchanged when nothing was written.
|
|
397
|
+
*/
|
|
398
|
+
export declare function recordSpawnedBrokerChild(reservation: NodeClaim, pid: number, env?: NodeJS.ProcessEnv): NodeClaim;
|
|
399
|
+
/**
|
|
400
|
+
* Hand a reservation to the verified broker process that now owns the state
|
|
401
|
+
* dir.
|
|
402
|
+
*
|
|
403
|
+
* Only the acquisition that created a generation ever rewrites it, so this is a
|
|
404
|
+
* conflict-free in-place update — but it is still conditional on that
|
|
405
|
+
* generation still being the highest: a `--force` takeover that landed while
|
|
406
|
+
* this broker was starting has already won the node id, and continuing would
|
|
407
|
+
* put two brokers back on one delivery socket. Losing here fails startup, which
|
|
408
|
+
* tears this broker down.
|
|
409
|
+
*
|
|
410
|
+
* @throws NodeClaimConflictError when the reservation no longer owns the node id.
|
|
411
|
+
*/
|
|
412
|
+
export declare function adoptNodeClaim(input: {
|
|
413
|
+
reservation: NodeClaim;
|
|
414
|
+
/** PID of the verified broker process holding the node-control socket. */
|
|
415
|
+
pid: number;
|
|
416
|
+
apiPort?: number;
|
|
417
|
+
brokerName?: string;
|
|
418
|
+
env?: NodeJS.ProcessEnv;
|
|
419
|
+
killProcess?: NodeClaimDependencies['killProcess'];
|
|
420
|
+
execCommand?: NodeClaimDependencies['execCommand'];
|
|
421
|
+
}): Promise<NodeClaim>;
|
|
422
|
+
/**
|
|
423
|
+
* Drop a claim this start owns, leaving its generation number spent.
|
|
424
|
+
*
|
|
425
|
+
* The record is REPLACED by a tombstone rather than removed. Removing it made
|
|
426
|
+
* the number reusable, and a suspended start could then re-create a generation
|
|
427
|
+
* that had since been handed to somebody else: it would pass the
|
|
428
|
+
* higher-generation check (its own file was the highest again) and prune the
|
|
429
|
+
* successor's live claim from its own stale scan, leaving two brokers alive on
|
|
430
|
+
* one node id with only one of them recorded. A tombstone keeps
|
|
431
|
+
* `max(generation)` from ever decreasing, so no number is issued twice; it
|
|
432
|
+
* parses as no claim at all, so the node id reads free immediately; and the
|
|
433
|
+
* next acquisition prunes it, so at most one spent file per node id is ever on
|
|
434
|
+
* disk.
|
|
435
|
+
*
|
|
436
|
+
* Only the acquisition that created a generation ever rewrites it, proven by
|
|
437
|
+
* `owner_token`, and the read and the write are adjacent syscalls with no await
|
|
438
|
+
* in between.
|
|
439
|
+
*/
|
|
440
|
+
export declare function releaseNodeClaim(claim: NodeClaim, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
441
|
+
/**
|
|
442
|
+
* Release the claims a broker pid held in a state dir. `node down` stops a
|
|
443
|
+
* broker it did not start, so it cannot name the node id the claim was written
|
|
444
|
+
* for — the pid and state dir it verified are what it has.
|
|
445
|
+
*
|
|
446
|
+
* A claim for that state dir whose every recorded pid is already dead is
|
|
447
|
+
* released too: that is the orphan left by a supervisor that was killed before
|
|
448
|
+
* it could record its broker's pid, and `down` has just proven the state dir's
|
|
449
|
+
* broker is gone.
|
|
450
|
+
*/
|
|
451
|
+
export declare function releaseNodeClaimsForBroker(input: {
|
|
452
|
+
pid: number;
|
|
453
|
+
stateDir: string;
|
|
454
|
+
env?: NodeJS.ProcessEnv;
|
|
455
|
+
killProcess?: NodeClaimDependencies['killProcess'];
|
|
456
|
+
execCommand?: NodeClaimDependencies['execCommand'];
|
|
457
|
+
}): Promise<NodeClaim[]>;
|
|
458
|
+
/** One-line description of the broker holding a claim, for operator output. */
|
|
459
|
+
export declare function describeNodeClaimHolder(claim: NodeClaim): string;
|
|
460
|
+
//# sourceMappingURL=node-claim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-claim.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/node-claim.ts"],"names":[],"mappings":"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"}
|