@serve.zone/interfaces 32.68.0 → 32.69.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.
@@ -0,0 +1,434 @@
1
+ import { canonicalizeStrictJson, compareWireTokens, createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
2
+ import { readDataElements, readDataProperties, readExactDataRecord } from '../private/dataproperties.js';
3
+ import { isClusterNodeId } from '../private/nodeidentifier.js';
4
+ import { isCanonicalTokenList } from '../private/tokenlist.js';
5
+ import { protocolRefusalHttpStatus, readProtocolOffer } from '../protocol.js';
6
+ import { isReleaseVersion } from './releaseversion.js';
7
+ /**
8
+ * Every node action, in canonical token order, so a list of them (a Spark's `actionExecutors`)
9
+ * is compared byte for byte.
10
+ */
11
+ export const nodeActionNames = Object.freeze([
12
+ 'reboot',
13
+ 'serveZoneServiceUpdate',
14
+ 'systemUpgrade',
15
+ ]);
16
+ const nodeActionNamesComplete = true;
17
+ void nodeActionNamesComplete;
18
+ export const isNodeActionName = (valueArg) => (nodeActionNames.some((nameArg) => nameArg === valueArg));
19
+ /**
20
+ * Whether each action state is still open or ended.
21
+ *
22
+ * The four values the Swarm-era carrier shipped keep their meaning: `pending` is the requested
23
+ * state of every action, `running` is a Swarm-era action its daemon started, and `succeeded` and
24
+ * `failed` end one. The node runtime's lifecycle (`INodeActionLifecycle`) adds the rest:
25
+ *
26
+ * - `claimed`: the node's Spark took the action (`claim`), stating the boot it took it in.
27
+ * - `executing`, `draining`, `rebooting`: the phase Spark reports, only ever forward
28
+ * (`sparkNodeActionPhaseOrder`).
29
+ * - `verifying`: Cloudly saw the evidence the action changed the node (`nodeActionVerification`)
30
+ * and waits for the node's runtime to come back.
31
+ * - `expired`: never claimed in its claim window, or a Swarm-era record no carrier can deliver
32
+ * (`TNodeActionExpiryReason`).
33
+ * - `cancelled`: an operator withdrew it while it was still `pending`.
34
+ * - `unverified`: its deadline passed before Cloudly could verify it. An operator judges the node;
35
+ * Cloudly never retries it.
36
+ */
37
+ export const nodeActionStatusKinds = Object.freeze({
38
+ pending: 'open',
39
+ running: 'open',
40
+ claimed: 'open',
41
+ executing: 'open',
42
+ draining: 'open',
43
+ rebooting: 'open',
44
+ verifying: 'open',
45
+ succeeded: 'terminal',
46
+ failed: 'terminal',
47
+ expired: 'terminal',
48
+ cancelled: 'terminal',
49
+ unverified: 'terminal',
50
+ });
51
+ /** Every open state, the ones the one-open-action-per-node rule counts. */
52
+ export const nodeActionOpenStatuses = Object.freeze(Object.keys(nodeActionStatusKinds)
53
+ .filter((statusArg) => nodeActionStatusKinds[statusArg] === 'open'));
54
+ export const isNodeActionOpen = (statusArg) => (nodeActionStatusKinds[statusArg] === 'open');
55
+ /**
56
+ * The phases Spark reports while it executes a claimed action, in the only order it may report
57
+ * them. A phase may be skipped, never repeated or reported after a later one.
58
+ */
59
+ export const sparkNodeActionPhaseOrder = Object.freeze(['claimed', 'executing', 'draining', 'rebooting']);
60
+ /**
61
+ * Time budgets of the node runtime's action lifecycle, in milliseconds.
62
+ *
63
+ * A requested action waits `claimWindowMs` for its node's Spark to claim it, then expires. A claimed
64
+ * action must be verified within its completion budget (`nodeActionCompletionBudgetMs`), measured
65
+ * from the claim, or it ends `unverified`. Each budget is the sum of the steps the action takes. One
66
+ * budget is read from Spark's code; every other one is chosen here, and each member says which.
67
+ */
68
+ export const nodeActionContract = Object.freeze({
69
+ /** Chosen: how long a requested action waits for its node's Spark to claim it (10 min). */
70
+ claimWindowMs: 600_000,
71
+ /**
72
+ * Spark code: `sparkNodeStopTimeoutSeconds`, the node unit's `TimeoutStopSec=` (522 s), in which
73
+ * Pallet stops every workload it runs.
74
+ */
75
+ drainMs: 522_000,
76
+ /** Chosen: a host's reboot until Spark reports from the new boot (10 min). */
77
+ bootMs: 600_000,
78
+ /**
79
+ * Chosen: how long Pallet takes to start and register again after the node runtime starts
80
+ * (712 s, from the 2026-11-05 survey; Spark states no start budget of its own).
81
+ */
82
+ startMs: 712_000,
83
+ /** Chosen: downloading and checking the target's node bundle before anything stops (10 min). */
84
+ downloadMs: 600_000,
85
+ /** Chosen: the host's package upgrade (30 min). */
86
+ hostUpgradeMs: 1_800_000,
87
+ /** Entries an action's timeline keeps; each state is entered at most once. */
88
+ maximumTimelineEntries: 16,
89
+ /** Characters of operator text one timeline entry or failure carries. */
90
+ maximumDetailLength: 512,
91
+ /** Services one impact names; a node holding more is answered `impact-unreadable`. */
92
+ maximumImpactServices: 1024,
93
+ /** Characters of one service name an impact carries. */
94
+ maximumServiceNameLength: 256,
95
+ });
96
+ /**
97
+ * How long a claimed action has until Cloudly must have verified it, from the claim.
98
+ *
99
+ * A reboot drains, boots and starts the node. An update downloads the target, then restarts the
100
+ * node runtime on it: a drain and a start, no boot. A host upgrade runs the package upgrade and,
101
+ * with `reboot`, the reboot after it.
102
+ */
103
+ export const nodeActionCompletionBudgetMs = (actionNameArg, rebootArg) => {
104
+ const c = nodeActionContract;
105
+ switch (actionNameArg) {
106
+ case 'reboot':
107
+ return c.drainMs + c.bootMs + c.startMs;
108
+ case 'serveZoneServiceUpdate':
109
+ return c.downloadMs + c.drainMs + c.startMs;
110
+ case 'systemUpgrade':
111
+ return c.hostUpgradeMs + (rebootArg ? c.drainMs + c.bootMs + c.startMs : 0);
112
+ }
113
+ };
114
+ /** Whether an action reboots the host, which is how Cloudly verifies it (`nodeActionVerification`). */
115
+ export const nodeActionReboots = (actionNameArg, rebootArg) => (actionNameArg === 'reboot' || (actionNameArg === 'systemUpgrade' && rebootArg));
116
+ export const nodeActionVerification = (actionNameArg, rebootArg) => nodeActionReboots(actionNameArg, rebootArg) ? 'new-boot'
117
+ : actionNameArg === 'serveZoneServiceUpdate' ? 'target-version' : 'upgrade-complete';
118
+ /** Whether the action restarts the node runtime and is verified by its return (see `nodeActionVerification`). */
119
+ export const nodeActionRestartsRuntime = (actionNameArg, rebootArg) => (nodeActionVerification(actionNameArg, rebootArg) !== 'upgrade-complete');
120
+ /**
121
+ * Why an action ended `failed`, each with the remedy an operator is told.
122
+ *
123
+ * The first group is stated by Spark (`TSparkNodeActionFailureCode`), the second by Cloudly.
124
+ */
125
+ export const nodeActionFailureRemedies = Object.freeze({
126
+ 'download-failed': 'Spark could not download the target release. Check that the node reaches '
127
+ + 'code.foss.global, then request the update again.',
128
+ 'installer-mismatch': 'The downloaded install.sh does not have the SHA-256 the target states. '
129
+ + 'Nothing was installed; set the fleet Spark target again, then request the update again.',
130
+ 'install-failed': 'The node bundle installer failed and left the installed release in place. '
131
+ + 'Read the node\'s Spark journal, then request the update again.',
132
+ 'drain-failed': 'Pallet did not stop its workloads within the stop budget. Read the node\'s '
133
+ + 'Pallet journal before requesting the action again.',
134
+ 'reboot-failed': 'The host did not accept the reboot. Read the node\'s Spark journal.',
135
+ 'host-upgrade-failed': 'The host package upgrade failed. Read the node\'s Spark journal, repair '
136
+ + 'the package state on the host, then request the upgrade again.',
137
+ 'executor-failed': 'Spark could not run the action. Read the node\'s Spark journal.',
138
+ 'reboot-required': 'The host upgrade finished and applying it needs a reboot. Request a system '
139
+ + 'upgrade with reboot, or a reboot, when the node may go down.',
140
+ 'target-below-node': 'The node runs a newer Spark than the action\'s target, so the update '
141
+ + 'would downgrade it. Set the fleet Spark target to the release the node should run.',
142
+ });
143
+ /** The failure codes Spark may state with a `fail` transition. */
144
+ export const sparkNodeActionFailureCodes = Object.freeze([
145
+ 'download-failed',
146
+ 'drain-failed',
147
+ 'executor-failed',
148
+ 'host-upgrade-failed',
149
+ 'install-failed',
150
+ 'installer-mismatch',
151
+ 'reboot-failed',
152
+ ]);
153
+ /** The SHA-256 (lowercase hex) of one impact's canonical JSON, which a confirmation names. */
154
+ export const computeNodeActionImpactDigest = async (impactArg) => {
155
+ const fail = (reasonArg) => { throw new Error(`Node action impact rejected: ${reasonArg}`); };
156
+ return createCanonicalJsonSha256Hex(canonicalizeStrictJson(impactArg, fail, 'node action impact'), fail);
157
+ };
158
+ /**
159
+ * Every reason Cloudly answers instead of admitting, previewing or cancelling a node action. Each
160
+ * travels by name as a `TypedResponseError`, and a refused request changes nothing.
161
+ */
162
+ export const nodeActionRefusals = Object.freeze([
163
+ /** No node of that id exists. */
164
+ 'node-missing',
165
+ /** The node reported through no Spark that carries actions: it never reported at all. */
166
+ 'node-action-undeliverable',
167
+ /** The node's Spark does not state that it executes this action (`ISparkNodeReport.actionExecutors`). */
168
+ 'node-action-unsupported',
169
+ /**
170
+ * System upgrade on a node runtime node: its Spark does not state the `systemUpgrade` executor,
171
+ * which Spark states only once its host-upgrade executor passed the lab qualification.
172
+ */
173
+ 'system-upgrade-unqualified',
174
+ /** The node already has an open action. */
175
+ 'node-action-open',
176
+ /** Another node of the cluster has an open action; a cluster runs one disruptive action at a time. */
177
+ 'cluster-action-open',
178
+ /** An update without a fleet Spark target. */
179
+ 'spark-target-unset',
180
+ /** An update whose target is below the Spark bundle version the node reports. */
181
+ 'spark-target-below-node',
182
+ /** An update of a node that is held at its release (`ISparkNodeTargetHold`). */
183
+ 'spark-target-held',
184
+ /** The request states no confirmation, or one that is not well-formed for this action. */
185
+ 'confirmation-invalid',
186
+ /** The typed hostname is not the hostname the node reports. */
187
+ 'confirmation-hostname-mismatch',
188
+ /** What the action interrupts changed since the preview the confirmation names. */
189
+ 'impact-changed',
190
+ /** An update whose confirmation names another fleet Spark target revision than the one Cloudly would freeze. */
191
+ 'target-changed',
192
+ /** The impact names a Corestore on the node and the confirmation does not consent to restarting it. */
193
+ 'corestore-consent-required',
194
+ /** Cloudly could not read what the action interrupts. */
195
+ 'impact-unreadable',
196
+ /** No action of that id on that node. */
197
+ 'node-action-missing',
198
+ /** Only a `pending` action is cancelled. */
199
+ 'node-action-not-cancellable',
200
+ /** The action moved since the revision the request names. */
201
+ 'node-action-revision-stale',
202
+ ]);
203
+ const maximumHostnameLength = 253;
204
+ const hostnamePattern = /^[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/;
205
+ const sha256HexPattern = /^[a-f0-9]{64}$/;
206
+ /**
207
+ * Validates one confirmation for one action and names every reason it is refused.
208
+ *
209
+ * Shape only: an update must state `targetRevision`, every other action must not. Whether it
210
+ * matches the node, the impact and the fleet target Cloudly reads now is Cloudly's decision
211
+ * (`confirmation-hostname-mismatch`, `impact-changed`, `target-changed`, `corestore-consent-required`).
212
+ */
213
+ export const validateNodeActionConfirmation = (valueArg, actionNameArg, pathArg = 'node action confirmation') => {
214
+ // An update names the target revision it confirmed; no other action does.
215
+ const update = actionNameArg === 'serveZoneServiceUpdate';
216
+ const value = readExactDataRecord(valueArg, ['confirmedHostname', 'impactDigest', 'corestoreRestart', 'reboot',
217
+ ...(update ? ['targetRevision'] : [])]);
218
+ if (value === null) {
219
+ return [update
220
+ ? `${pathArg} must use its exact schema, with the targetRevision the preview showed`
221
+ : `${pathArg} must use its exact schema, without a targetRevision`];
222
+ }
223
+ const errors = [];
224
+ if (update && (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 1)) {
225
+ errors.push(`${pathArg} targetRevision must be a positive integer`);
226
+ }
227
+ if (typeof value.confirmedHostname !== 'string' || value.confirmedHostname.length > maximumHostnameLength
228
+ || !hostnamePattern.test(value.confirmedHostname)) {
229
+ errors.push(`${pathArg} confirmedHostname must be a lowercase hostname`);
230
+ }
231
+ if (typeof value.impactDigest !== 'string' || !sha256HexPattern.test(value.impactDigest)) {
232
+ errors.push(`${pathArg} impactDigest must be a lowercase SHA-256 hex digest`);
233
+ }
234
+ if (typeof value.corestoreRestart !== 'boolean')
235
+ errors.push(`${pathArg} corestoreRestart must be a boolean`);
236
+ if (typeof value.reboot !== 'boolean') {
237
+ errors.push(`${pathArg} reboot must be a boolean`);
238
+ }
239
+ else if (value.reboot && actionNameArg !== 'systemUpgrade') {
240
+ errors.push(`${pathArg} reboot is stated for systemUpgrade only`);
241
+ }
242
+ return errors;
243
+ };
244
+ /**
245
+ * The node runtime's action routes.
246
+ *
247
+ * Spark asks the offer route after each accepted heartbeat and states every step on the transition
248
+ * route. Both are authenticated with the node's Spark credential exactly like
249
+ * `sparkNodeHeartbeatContract` and judged in the same server order: the offer first, within
250
+ * `maxRequestBytes`, answered with `protocolRefusalStatus` and an `IProtocolRefusal` body on
251
+ * refusal, before the node is authenticated. The server's offer on these routes names, as its
252
+ * minimum, the release that introduced them, so a Spark below it is refused by name and is never
253
+ * handed an action.
254
+ */
255
+ export const sparkNodeActionContracts = Object.freeze({
256
+ offer: Object.freeze({
257
+ endpoint: '/spark/nodes/action',
258
+ maxRequestBytes: 1024,
259
+ maxResponseBytes: 4096,
260
+ }),
261
+ transition: Object.freeze({
262
+ endpoint: '/spark/nodes/action/transition',
263
+ maxRequestBytes: 2048,
264
+ maxResponseBytes: 4096,
265
+ }),
266
+ protocolRefusalStatus: protocolRefusalHttpStatus,
267
+ });
268
+ const fail = () => { throw new Error('Spark node action contract rejected.'); };
269
+ function requireValue(valueArg) {
270
+ if (!valueArg)
271
+ fail();
272
+ }
273
+ const record = (valueArg, keysArg) => {
274
+ const result = readExactDataRecord(valueArg, keysArg);
275
+ requireValue(result !== null);
276
+ return result;
277
+ };
278
+ const nodeId = (valueArg) => (isClusterNodeId(valueArg) ? valueArg : fail());
279
+ const nodeToken = (valueArg) => {
280
+ requireValue(typeof valueArg === 'string' && /^secureToken[a-f0-9]{64}$/.test(valueArg));
281
+ return valueArg;
282
+ };
283
+ const integer = (valueArg, minimumArg) => {
284
+ requireValue(Number.isSafeInteger(valueArg) && !Object.is(valueArg, -0) && valueArg >= minimumArg);
285
+ return valueArg;
286
+ };
287
+ /** An action id as Cloudly mints them: a short URL-safe token. */
288
+ const actionId = (valueArg) => {
289
+ requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,64}$/.test(valueArg));
290
+ return valueArg;
291
+ };
292
+ /** The kernel's `boot_id`: one lowercase UUID. */
293
+ export const isHostBootId = (valueArg) => (typeof valueArg === 'string'
294
+ && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(valueArg));
295
+ const bootId = (valueArg) => (isHostBootId(valueArg) ? valueArg : fail());
296
+ const operatorText = (valueArg) => {
297
+ requireValue(typeof valueArg === 'string' && valueArg.length <= nodeActionContract.maximumDetailLength
298
+ && /^[\x20-\x7e]*$/.test(valueArg));
299
+ return valueArg;
300
+ };
301
+ const snapshotTarget = (valueArg) => {
302
+ const value = record(valueArg, ['version', 'installerSha256', 'targetRevision']);
303
+ requireValue(isReleaseVersion(value.version));
304
+ requireValue(typeof value.installerSha256 === 'string' && sha256HexPattern.test(value.installerSha256));
305
+ return Object.freeze({
306
+ version: value.version, installerSha256: value.installerSha256,
307
+ targetRevision: integer(value.targetRevision, 1),
308
+ });
309
+ };
310
+ const assignmentStatuses = ['pending', 'claimed', 'executing', 'draining', 'rebooting', 'verifying'];
311
+ /** One assignment, exactly its keys; detached and frozen. */
312
+ export const snapshotSparkNodeActionAssignment = (valueArg) => {
313
+ const value = record(valueArg, ['id', 'actionName', 'status', 'revision', 'deadlineAt', 'reboot', 'target', 'claim']);
314
+ requireValue(isNodeActionName(value.actionName));
315
+ const status = assignmentStatuses.find((statusArg) => statusArg === value.status) ?? fail();
316
+ requireValue(typeof value.reboot === 'boolean' && (!value.reboot || value.actionName === 'systemUpgrade'));
317
+ const target = value.target === null ? null : snapshotTarget(value.target);
318
+ requireValue((target !== null) === (value.actionName === 'serveZoneServiceUpdate'));
319
+ const claim = value.claim === null ? null : Object.freeze({ bootId: bootId(record(value.claim, ['bootId']).bootId) });
320
+ requireValue((claim === null) === (status === 'pending'));
321
+ return Object.freeze({
322
+ id: actionId(value.id), actionName: value.actionName, status,
323
+ revision: integer(value.revision, 1), deadlineAt: integer(value.deadlineAt, 1),
324
+ reboot: value.reboot, target, claim,
325
+ });
326
+ };
327
+ export const snapshotSparkNodeActionOfferRequest = (valueArg) => {
328
+ const value = record(valueArg, ['nodeId', 'nodeToken', 'protocol']);
329
+ return Object.freeze({
330
+ nodeId: nodeId(value.nodeId), nodeToken: nodeToken(value.nodeToken), protocol: readProtocolOffer(value),
331
+ });
332
+ };
333
+ export const snapshotSparkNodeActionOffer = (valueArg) => {
334
+ const value = record(valueArg, ['nodeId', 'action', 'protocol']);
335
+ return Object.freeze({
336
+ nodeId: nodeId(value.nodeId),
337
+ action: value.action === null ? null : snapshotSparkNodeActionAssignment(value.action),
338
+ protocol: readProtocolOffer(value),
339
+ });
340
+ };
341
+ const snapshotTransition = (valueArg) => {
342
+ const properties = readDataProperties(valueArg);
343
+ requireValue(properties !== null);
344
+ switch (properties.kind) {
345
+ case 'claim':
346
+ return Object.freeze({ kind: 'claim', bootId: bootId(record(valueArg, ['kind', 'bootId']).bootId) });
347
+ case 'phase': {
348
+ const phase = record(valueArg, ['kind', 'phase']).phase;
349
+ const known = sparkNodeActionPhaseOrder.find((phaseArg) => phaseArg !== 'claimed' && phaseArg === phase);
350
+ return Object.freeze({ kind: 'phase', phase: (known ?? fail()) });
351
+ }
352
+ case 'complete':
353
+ record(valueArg, ['kind']);
354
+ return Object.freeze({ kind: 'complete' });
355
+ case 'fail': {
356
+ const value = record(valueArg, ['kind', 'code', 'detail']);
357
+ const code = sparkNodeActionFailureCodes.find((codeArg) => codeArg === value.code) ?? fail();
358
+ return Object.freeze({ kind: 'fail', code, detail: operatorText(value.detail) });
359
+ }
360
+ default:
361
+ return fail();
362
+ }
363
+ };
364
+ export const snapshotSparkNodeActionTransitionRequest = (valueArg) => {
365
+ const value = record(valueArg, ['nodeId', 'nodeToken', 'protocol', 'actionId', 'expectedRevision', 'transition']);
366
+ return Object.freeze({
367
+ nodeId: nodeId(value.nodeId), nodeToken: nodeToken(value.nodeToken), protocol: readProtocolOffer(value),
368
+ actionId: actionId(value.actionId), expectedRevision: integer(value.expectedRevision, 1),
369
+ transition: snapshotTransition(value.transition),
370
+ });
371
+ };
372
+ const transitionRefusals = [
373
+ 'action-unknown', 'action-closed', 'revision-stale', 'transition-invalid',
374
+ ];
375
+ export const snapshotSparkNodeActionTransitionAnswer = (valueArg) => {
376
+ const properties = readDataProperties(valueArg);
377
+ requireValue(properties !== null);
378
+ if (properties.outcome === 'refused') {
379
+ const value = record(valueArg, ['outcome', 'reason', 'action', 'protocol']);
380
+ const reason = transitionRefusals.find((reasonArg) => reasonArg === value.reason) ?? fail();
381
+ return Object.freeze({
382
+ outcome: 'refused', reason,
383
+ action: value.action === null ? null : snapshotSparkNodeActionAssignment(value.action),
384
+ protocol: readProtocolOffer(value),
385
+ });
386
+ }
387
+ const value = record(valueArg, ['outcome', 'action', 'protocol']);
388
+ requireValue(value.outcome === 'applied' || value.outcome === 'replayed');
389
+ return Object.freeze({
390
+ outcome: value.outcome,
391
+ action: value.action === null ? null : snapshotSparkNodeActionAssignment(value.action),
392
+ protocol: readProtocolOffer(value),
393
+ });
394
+ };
395
+ /**
396
+ * Whether an open action admits one transition from Spark, judged from its state alone.
397
+ *
398
+ * `claim` takes a `pending` action. A `phase` moves strictly forward in `sparkNodeActionPhaseOrder`
399
+ * from `claimed` or an earlier phase, and only into a phase the action has: `rebooting` only for
400
+ * an action that reboots, `draining` for every action but a host upgrade without a reboot.
401
+ * `complete` ends the execution of a host upgrade without a reboot. `fail` ends any claimed action
402
+ * that Cloudly is not yet verifying.
403
+ */
404
+ export const admitsSparkNodeActionTransition = (actionArg, transitionArg) => {
405
+ const executing = sparkNodeActionPhaseOrder.indexOf(actionArg.status);
406
+ switch (transitionArg.kind) {
407
+ case 'claim':
408
+ return actionArg.status === 'pending';
409
+ case 'phase': {
410
+ if (executing < 0)
411
+ return false;
412
+ const next = sparkNodeActionPhaseOrder.indexOf(transitionArg.phase);
413
+ if (next <= executing)
414
+ return false;
415
+ if (transitionArg.phase === 'rebooting')
416
+ return nodeActionReboots(actionArg.actionName, actionArg.reboot);
417
+ if (transitionArg.phase === 'draining')
418
+ return nodeActionRestartsRuntime(actionArg.actionName, actionArg.reboot);
419
+ return actionArg.actionName !== 'reboot';
420
+ }
421
+ case 'complete':
422
+ return executing >= 1 && nodeActionVerification(actionArg.actionName, actionArg.reboot) === 'upgrade-complete';
423
+ case 'fail':
424
+ return executing >= 0;
425
+ }
426
+ };
427
+ /** True when a value is a canonical list of action names: known names, strictly increasing, at least one. */
428
+ export const isNodeActionNameList = (valueArg) => {
429
+ const elements = readDataElements(valueArg, nodeActionNames.length);
430
+ return elements !== null && elements.length > 0 && isCanonicalTokenList(elements, nodeActionNames);
431
+ };
432
+ /** Sorts action names into canonical order, so a list built from a set compares byte for byte. */
433
+ export const sortNodeActionNames = (namesArg) => ([...new Set(namesArg)].sort(compareWireTokens));
434
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWFjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvbm9kZWFjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsaUJBQWlCLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUN0SCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUN6RyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDL0QsT0FBTyxFQUFFLHlCQUF5QixFQUFFLGlCQUFpQixFQUF1QixNQUFNLGdCQUFnQixDQUFDO0FBRW5HLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXZEOzs7R0FHRztBQUNILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzNDLFFBQVE7SUFDUix3QkFBd0I7SUFDeEIsZUFBZTtDQUM4QixDQUFDLENBQUM7QUFJakQsTUFBTSx1QkFBdUIsR0FBNkIsSUFBSSxDQUFDO0FBQy9ELEtBQUssdUJBQXVCLENBQUM7QUFFN0IsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxRQUFpQixFQUErQixFQUFFLENBQUMsQ0FDbEYsZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsT0FBTyxLQUFLLFFBQVEsQ0FBQyxDQUN4RCxDQUFDO0FBRUY7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNqRCxPQUFPLEVBQUUsTUFBTTtJQUNmLE9BQU8sRUFBRSxNQUFNO0lBQ2YsT0FBTyxFQUFFLE1BQU07SUFDZixTQUFTLEVBQUUsTUFBTTtJQUNqQixRQUFRLEVBQUUsTUFBTTtJQUNoQixTQUFTLEVBQUUsTUFBTTtJQUNqQixTQUFTLEVBQUUsTUFBTTtJQUNqQixTQUFTLEVBQUUsVUFBVTtJQUNyQixNQUFNLEVBQUUsVUFBVTtJQUNsQixPQUFPLEVBQUUsVUFBVTtJQUNuQixTQUFTLEVBQUUsVUFBVTtJQUNyQixVQUFVLEVBQUUsVUFBVTtDQUMyQyxDQUFDLENBQUM7QUFFckUsMkVBQTJFO0FBQzNFLE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxxQkFBcUIsQ0FBeUI7S0FDNUcsTUFBTSxDQUFDLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsS0FBSyxNQUFNLENBQUMsQ0FBQyxDQUFDO0FBRXZFLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsU0FBNEIsRUFBVyxFQUFFLENBQUMsQ0FDekUscUJBQXFCLENBQUMsU0FBUyxDQUFDLEtBQUssTUFBTSxDQUM1QyxDQUFDO0FBRUY7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLFNBQVMsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBVSxDQUFDLENBQUM7QUFLbkg7Ozs7Ozs7R0FPRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDOUMsMkZBQTJGO0lBQzNGLGFBQWEsRUFBRSxPQUFPO0lBQ3RCOzs7T0FHRztJQUNILE9BQU8sRUFBRSxPQUFPO0lBQ2hCLDhFQUE4RTtJQUM5RSxNQUFNLEVBQUUsT0FBTztJQUNmOzs7T0FHRztJQUNILE9BQU8sRUFBRSxPQUFPO0lBQ2hCLGdHQUFnRztJQUNoRyxVQUFVLEVBQUUsT0FBTztJQUNuQixtREFBbUQ7SUFDbkQsYUFBYSxFQUFFLFNBQVM7SUFDeEIsOEVBQThFO0lBQzlFLHNCQUFzQixFQUFFLEVBQUU7SUFDMUIseUVBQXlFO0lBQ3pFLG1CQUFtQixFQUFFLEdBQUc7SUFDeEIsc0ZBQXNGO0lBQ3RGLHFCQUFxQixFQUFFLElBQUk7SUFDM0Isd0RBQXdEO0lBQ3hELHdCQUF3QixFQUFFLEdBQUc7Q0FDckIsQ0FBQyxDQUFDO0FBRVo7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sNEJBQTRCLEdBQUcsQ0FBQyxhQUE4QixFQUFFLFNBQWtCLEVBQVUsRUFBRTtJQUN6RyxNQUFNLENBQUMsR0FBRyxrQkFBa0IsQ0FBQztJQUM3QixRQUFRLGFBQWEsRUFBRSxDQUFDO1FBQ3RCLEtBQUssUUFBUTtZQUNYLE9BQU8sQ0FBQyxDQUFDLE9BQU8sR0FBRyxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUM7UUFDMUMsS0FBSyx3QkFBd0I7WUFDM0IsT0FBTyxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQyxPQUFPLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUM5QyxLQUFLLGVBQWU7WUFDbEIsT0FBTyxDQUFDLENBQUMsYUFBYSxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxHQUFHLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDaEYsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLHVHQUF1RztBQUN2RyxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLGFBQThCLEVBQUUsU0FBa0IsRUFBVyxFQUFFLENBQUMsQ0FDaEcsYUFBYSxLQUFLLFFBQVEsSUFBSSxDQUFDLGFBQWEsS0FBSyxlQUFlLElBQUksU0FBUyxDQUFDLENBQy9FLENBQUM7QUFvQkYsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsQ0FDcEMsYUFBOEIsRUFDOUIsU0FBa0IsRUFDTyxFQUFFLENBQUMsaUJBQWlCLENBQUMsYUFBYSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVO0lBQ3BGLENBQUMsQ0FBQyxhQUFhLEtBQUssd0JBQXdCLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQztBQUV2RixpSEFBaUg7QUFDakgsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsQ0FBQyxhQUE4QixFQUFFLFNBQWtCLEVBQVcsRUFBRSxDQUFDLENBQ3hHLHNCQUFzQixDQUFDLGFBQWEsRUFBRSxTQUFTLENBQUMsS0FBSyxrQkFBa0IsQ0FDeEUsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSx5QkFBeUIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3JELGlCQUFpQixFQUFFLDJFQUEyRTtVQUMxRixrREFBa0Q7SUFDdEQsb0JBQW9CLEVBQUUseUVBQXlFO1VBQzNGLHlGQUF5RjtJQUM3RixnQkFBZ0IsRUFBRSw0RUFBNEU7VUFDMUYsZ0VBQWdFO0lBQ3BFLGNBQWMsRUFBRSw2RUFBNkU7VUFDekYsb0RBQW9EO0lBQ3hELGVBQWUsRUFBRSxxRUFBcUU7SUFDdEYscUJBQXFCLEVBQUUsMEVBQTBFO1VBQzdGLGdFQUFnRTtJQUNwRSxpQkFBaUIsRUFBRSxpRUFBaUU7SUFDcEYsaUJBQWlCLEVBQUUsNkVBQTZFO1VBQzVGLDhEQUE4RDtJQUNsRSxtQkFBbUIsRUFBRSx1RUFBdUU7VUFDeEYsb0ZBQW9GO0NBQ2hGLENBQUMsQ0FBQztBQUlaLGtFQUFrRTtBQUNsRSxNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3ZELGlCQUFpQjtJQUNqQixjQUFjO0lBQ2QsaUJBQWlCO0lBQ2pCLHFCQUFxQjtJQUNyQixnQkFBZ0I7SUFDaEIsb0JBQW9CO0lBQ3BCLGVBQWU7Q0FDcUMsQ0FBQyxDQUFDO0FBNEZ4RCw4RkFBOEY7QUFDOUYsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsS0FBSyxFQUFFLFNBQTRCLEVBQW1CLEVBQUU7SUFDbkcsTUFBTSxJQUFJLEdBQUcsQ0FBQyxTQUFpQixFQUFTLEVBQUUsR0FBRyxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdHLE9BQU8sNEJBQTRCLENBQUMsc0JBQXNCLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxvQkFBb0IsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQzNHLENBQUMsQ0FBQztBQW9ERjs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzlDLGlDQUFpQztJQUNqQyxjQUFjO0lBQ2QseUZBQXlGO0lBQ3pGLDJCQUEyQjtJQUMzQix5R0FBeUc7SUFDekcseUJBQXlCO0lBQ3pCOzs7T0FHRztJQUNILDRCQUE0QjtJQUM1QiwyQ0FBMkM7SUFDM0Msa0JBQWtCO0lBQ2xCLHNHQUFzRztJQUN0RyxxQkFBcUI7SUFDckIsOENBQThDO0lBQzlDLG9CQUFvQjtJQUNwQixpRkFBaUY7SUFDakYseUJBQXlCO0lBQ3pCLGdGQUFnRjtJQUNoRixtQkFBbUI7SUFDbkIsMEZBQTBGO0lBQzFGLHNCQUFzQjtJQUN0QiwrREFBK0Q7SUFDL0QsZ0NBQWdDO0lBQ2hDLG1GQUFtRjtJQUNuRixnQkFBZ0I7SUFDaEIsZ0hBQWdIO0lBQ2hILGdCQUFnQjtJQUNoQix1R0FBdUc7SUFDdkcsNEJBQTRCO0lBQzVCLHlEQUF5RDtJQUN6RCxtQkFBbUI7SUFDbkIseUNBQXlDO0lBQ3pDLHFCQUFxQjtJQUNyQiw0Q0FBNEM7SUFDNUMsNkJBQTZCO0lBQzdCLDZEQUE2RDtJQUM3RCw0QkFBNEI7Q0FDcEIsQ0FBQyxDQUFDO0FBcUJaLE1BQU0scUJBQXFCLEdBQUcsR0FBRyxDQUFDO0FBQ2xDLE1BQU0sZUFBZSxHQUFHLDBDQUEwQyxDQUFDO0FBQ25FLE1BQU0sZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUM7QUFFMUM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sOEJBQThCLEdBQUcsQ0FDNUMsUUFBaUIsRUFDakIsYUFBOEIsRUFDOUIsT0FBTyxHQUFHLDBCQUEwQixFQUMxQixFQUFFO0lBQ1osMEVBQTBFO0lBQzFFLE1BQU0sTUFBTSxHQUFHLGFBQWEsS0FBSyx3QkFBd0IsQ0FBQztJQUMxRCxNQUFNLEtBQUssR0FBRyxtQkFBbUIsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxtQkFBbUIsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsUUFBUTtRQUM1RyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxQyxJQUFJLEtBQUssS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUNuQixPQUFPLENBQUMsTUFBTTtnQkFDWixDQUFDLENBQUMsR0FBRyxPQUFPLHdFQUF3RTtnQkFDcEYsQ0FBQyxDQUFDLEdBQUcsT0FBTyxzREFBc0QsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7SUFDRCxNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7SUFDNUIsSUFBSSxNQUFNLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxJQUFLLEtBQUssQ0FBQyxjQUF5QixHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDcEcsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sNENBQTRDLENBQUMsQ0FBQztJQUN0RSxDQUFDO0lBQ0QsSUFBSSxPQUFPLEtBQUssQ0FBQyxpQkFBaUIsS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLGlCQUFpQixDQUFDLE1BQU0sR0FBRyxxQkFBcUI7V0FDcEcsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFLENBQUM7UUFDcEQsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8saURBQWlELENBQUMsQ0FBQztJQUMzRSxDQUFDO0lBQ0QsSUFBSSxPQUFPLEtBQUssQ0FBQyxZQUFZLEtBQUssUUFBUSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1FBQ3pGLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxPQUFPLHNEQUFzRCxDQUFDLENBQUM7SUFDaEYsQ0FBQztJQUNELElBQUksT0FBTyxLQUFLLENBQUMsZ0JBQWdCLEtBQUssU0FBUztRQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxPQUFPLHFDQUFxQyxDQUFDLENBQUM7SUFDOUcsSUFBSSxPQUFPLEtBQUssQ0FBQyxNQUFNLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDdEMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sMkJBQTJCLENBQUMsQ0FBQztJQUNyRCxDQUFDO1NBQU0sSUFBSSxLQUFLLENBQUMsTUFBTSxJQUFJLGFBQWEsS0FBSyxlQUFlLEVBQUUsQ0FBQztRQUM3RCxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTywwQ0FBMEMsQ0FBQyxDQUFDO0lBQ3BFLENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUM7QUE0RkY7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDcEQsS0FBSyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDbkIsUUFBUSxFQUFFLHFCQUFxQjtRQUMvQixlQUFlLEVBQUUsSUFBSTtRQUNyQixnQkFBZ0IsRUFBRSxJQUFJO0tBQ2QsQ0FBQztJQUNYLFVBQVUsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ3hCLFFBQVEsRUFBRSxnQ0FBZ0M7UUFDMUMsZUFBZSxFQUFFLElBQUk7UUFDckIsZ0JBQWdCLEVBQUUsSUFBSTtLQUNkLENBQUM7SUFDWCxxQkFBcUIsRUFBRSx5QkFBeUI7Q0FDeEMsQ0FBQyxDQUFDO0FBRVosTUFBTSxJQUFJLEdBQUcsR0FBVSxFQUFFLEdBQUcsTUFBTSxJQUFJLEtBQUssQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3ZGLFNBQVMsWUFBWSxDQUFDLFFBQWlCO0lBQ3JDLElBQUksQ0FBQyxRQUFRO1FBQUUsSUFBSSxFQUFFLENBQUM7QUFDeEIsQ0FBQztBQUNELE1BQU0sTUFBTSxHQUFHLENBQUMsUUFBaUIsRUFBRSxPQUEwQixFQUEyQixFQUFFO0lBQ3hGLE1BQU0sTUFBTSxHQUFHLG1CQUFtQixDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUN0RCxZQUFZLENBQUMsTUFBTSxLQUFLLElBQUksQ0FBQyxDQUFDO0lBQzlCLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMsQ0FBQztBQUNGLE1BQU0sTUFBTSxHQUFHLENBQUMsUUFBaUIsRUFBVSxFQUFFLENBQUMsQ0FBQyxlQUFlLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztBQUM5RixNQUFNLFNBQVMsR0FBRyxDQUFDLFFBQWlCLEVBQVUsRUFBRTtJQUM5QyxZQUFZLENBQUMsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLDJCQUEyQixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQ3pGLE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUMsQ0FBQztBQUNGLE1BQU0sT0FBTyxHQUFHLENBQUMsUUFBaUIsRUFBRSxVQUFrQixFQUFVLEVBQUU7SUFDaEUsWUFBWSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFLLFFBQW1CLElBQUksVUFBVSxDQUFDLENBQUM7SUFDL0csT0FBTyxRQUFrQixDQUFDO0FBQzVCLENBQUMsQ0FBQztBQUNGLGtFQUFrRTtBQUNsRSxNQUFNLFFBQVEsR0FBRyxDQUFDLFFBQWlCLEVBQVUsRUFBRTtJQUM3QyxZQUFZLENBQUMsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLHVCQUF1QixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQ3JGLE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUMsQ0FBQztBQUNGLGtEQUFrRDtBQUNsRCxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxRQUFpQixFQUFzQixFQUFFLENBQUMsQ0FDckUsT0FBTyxRQUFRLEtBQUssUUFBUTtPQUN6QixnRUFBZ0UsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQ25GLENBQUM7QUFDRixNQUFNLE1BQU0sR0FBRyxDQUFDLFFBQWlCLEVBQVUsRUFBRSxDQUFDLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7QUFDM0YsTUFBTSxZQUFZLEdBQUcsQ0FBQyxRQUFpQixFQUFVLEVBQUU7SUFDakQsWUFBWSxDQUFDLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxRQUFRLENBQUMsTUFBTSxJQUFJLGtCQUFrQixDQUFDLG1CQUFtQjtXQUNqRyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztJQUN0QyxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDLENBQUM7QUFFRixNQUFNLGNBQWMsR0FBRyxDQUFDLFFBQWlCLEVBQXFCLEVBQUU7SUFDOUQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDLFNBQVMsRUFBRSxpQkFBaUIsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDLENBQUM7SUFDakYsWUFBWSxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzlDLFlBQVksQ0FBQyxPQUFPLEtBQUssQ0FBQyxlQUFlLEtBQUssUUFBUSxJQUFJLGdCQUFnQixDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQztJQUN4RyxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDbkIsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUUsZUFBZSxFQUFFLEtBQUssQ0FBQyxlQUFlO1FBQzlELGNBQWMsRUFBRSxPQUFPLENBQUMsS0FBSyxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7S0FDakQsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDO0FBRUYsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFNBQVMsRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsV0FBVyxDQUFVLENBQUM7QUFFOUcsNkRBQTZEO0FBQzdELE1BQU0sQ0FBQyxNQUFNLGlDQUFpQyxHQUFHLENBQUMsUUFBaUIsRUFBOEIsRUFBRTtJQUNqRyxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsSUFBSSxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDdEgsWUFBWSxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sTUFBTSxHQUFHLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsU0FBUyxLQUFLLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUM1RixZQUFZLENBQUMsT0FBTyxLQUFLLENBQUMsTUFBTSxLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sSUFBSSxLQUFLLENBQUMsVUFBVSxLQUFLLGVBQWUsQ0FBQyxDQUFDLENBQUM7SUFDM0csTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU0sS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUMzRSxZQUFZLENBQUMsQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsVUFBVSxLQUFLLHdCQUF3QixDQUFDLENBQUMsQ0FBQztJQUNwRixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3RILFlBQVksQ0FBQyxDQUFDLEtBQUssS0FBSyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDO0lBQzFELE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztRQUNuQixFQUFFLEVBQUUsUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsRUFBRSxVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVUsRUFBRSxNQUFNO1FBQzVELFFBQVEsRUFBRSxPQUFPLENBQUMsS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1FBQzlFLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxLQUFLO0tBQ3BDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLG1DQUFtQyxHQUFHLENBQUMsUUFBaUIsRUFBZ0MsRUFBRTtJQUNyRyxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFLFdBQVcsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ3BFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztRQUNuQixNQUFNLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRSxTQUFTLEVBQUUsU0FBUyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsRUFBRSxRQUFRLEVBQUUsaUJBQWlCLENBQUMsS0FBSyxDQUFDO0tBQ3hHLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLDRCQUE0QixHQUFHLENBQUMsUUFBaUIsRUFBeUIsRUFBRTtJQUN2RixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ2pFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztRQUNuQixNQUFNLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDNUIsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGlDQUFpQyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDdEYsUUFBUSxFQUFFLGlCQUFpQixDQUFDLEtBQUssQ0FBQztLQUNuQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixNQUFNLGtCQUFrQixHQUFHLENBQUMsUUFBaUIsRUFBOEIsRUFBRTtJQUMzRSxNQUFNLFVBQVUsR0FBRyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNoRCxZQUFZLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQyxDQUFDO0lBQ2xDLFFBQVEsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3hCLEtBQUssT0FBTztZQUNWLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZHLEtBQUssT0FBTyxFQUFFLENBQUM7WUFDYixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO1lBQ3hELE1BQU0sS0FBSyxHQUFHLHlCQUF5QixDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsUUFBUSxLQUFLLFNBQVMsSUFBSSxRQUFRLEtBQUssS0FBSyxDQUFDLENBQUM7WUFDekcsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQyxLQUFLLElBQUksSUFBSSxFQUFFLENBQTBCLEVBQUUsQ0FBQyxDQUFDO1FBQzdGLENBQUM7UUFDRCxLQUFLLFVBQVU7WUFDYixNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztZQUMzQixPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztRQUM3QyxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ1osTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztZQUMzRCxNQUFNLElBQUksR0FBRywyQkFBMkIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDN0YsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLFlBQVksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ25GLENBQUM7UUFDRDtZQUNFLE9BQU8sSUFBSSxFQUFFLENBQUM7SUFDbEIsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLHdDQUF3QyxHQUFHLENBQUMsUUFBaUIsRUFBcUMsRUFBRTtJQUMvRyxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLGtCQUFrQixFQUFFLFlBQVksQ0FBQyxDQUFDLENBQUM7SUFDbEgsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ25CLE1BQU0sRUFBRSxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxpQkFBaUIsQ0FBQyxLQUFLLENBQUM7UUFDdkcsUUFBUSxFQUFFLFFBQVEsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEVBQUUsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLENBQUM7UUFDeEYsVUFBVSxFQUFFLGtCQUFrQixDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUM7S0FDakQsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDO0FBRUYsTUFBTSxrQkFBa0IsR0FBaUQ7SUFDdkUsZ0JBQWdCLEVBQUUsZUFBZSxFQUFFLGdCQUFnQixFQUFFLG9CQUFvQjtDQUMxRSxDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0sdUNBQXVDLEdBQUcsQ0FBQyxRQUFpQixFQUFvQyxFQUFFO0lBQzdHLE1BQU0sVUFBVSxHQUFHLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2hELFlBQVksQ0FBQyxVQUFVLEtBQUssSUFBSSxDQUFDLENBQUM7SUFDbEMsSUFBSSxVQUFVLENBQUMsT0FBTyxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3JDLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO1FBQzVFLE1BQU0sTUFBTSxHQUFHLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsU0FBUyxLQUFLLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUM1RixPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUM7WUFDbkIsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNO1lBQzFCLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxpQ0FBaUMsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDO1lBQ3RGLFFBQVEsRUFBRSxpQkFBaUIsQ0FBQyxLQUFLLENBQUM7U0FDbkMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUNELE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUM7SUFDbEUsWUFBWSxDQUFDLEtBQUssQ0FBQyxPQUFPLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxPQUFPLEtBQUssVUFBVSxDQUFDLENBQUM7SUFDMUUsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ25CLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBaUM7UUFDaEQsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGlDQUFpQyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDdEYsUUFBUSxFQUFFLGlCQUFpQixDQUFDLEtBQUssQ0FBQztLQUNuQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRjs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLENBQzdDLFNBQStFLEVBQy9FLGFBQXlDLEVBQ2hDLEVBQUU7SUFDWCxNQUFNLFNBQVMsR0FBSSx5QkFBK0MsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzdGLFFBQVEsYUFBYSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzNCLEtBQUssT0FBTztZQUNWLE9BQU8sU0FBUyxDQUFDLE1BQU0sS0FBSyxTQUFTLENBQUM7UUFDeEMsS0FBSyxPQUFPLEVBQUUsQ0FBQztZQUNiLElBQUksU0FBUyxHQUFHLENBQUM7Z0JBQUUsT0FBTyxLQUFLLENBQUM7WUFDaEMsTUFBTSxJQUFJLEdBQUcseUJBQXlCLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNwRSxJQUFJLElBQUksSUFBSSxTQUFTO2dCQUFFLE9BQU8sS0FBSyxDQUFDO1lBQ3BDLElBQUksYUFBYSxDQUFDLEtBQUssS0FBSyxXQUFXO2dCQUFFLE9BQU8saUJBQWlCLENBQUMsU0FBUyxDQUFDLFVBQVUsRUFBRSxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDMUcsSUFBSSxhQUFhLENBQUMsS0FBSyxLQUFLLFVBQVU7Z0JBQUUsT0FBTyx5QkFBeUIsQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNqSCxPQUFPLFNBQVMsQ0FBQyxVQUFVLEtBQUssUUFBUSxDQUFDO1FBQzNDLENBQUM7UUFDRCxLQUFLLFVBQVU7WUFDYixPQUFPLFNBQVMsSUFBSSxDQUFDLElBQUksc0JBQXNCLENBQUMsU0FBUyxDQUFDLFVBQVUsRUFBRSxTQUFTLENBQUMsTUFBTSxDQUFDLEtBQUssa0JBQWtCLENBQUM7UUFDakgsS0FBSyxNQUFNO1lBQ1QsT0FBTyxTQUFTLElBQUksQ0FBQyxDQUFDO0lBQzFCLENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRiw2R0FBNkc7QUFDN0csTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxRQUFpQixFQUFpQyxFQUFFO0lBQ3ZGLE1BQU0sUUFBUSxHQUFHLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDcEUsT0FBTyxRQUFRLEtBQUssSUFBSSxJQUFJLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxlQUFlLENBQUMsQ0FBQztBQUNyRyxDQUFDLENBQUM7QUFFRixrR0FBa0c7QUFDbEcsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxRQUFtQyxFQUFxQixFQUFFLENBQUMsQ0FDN0YsQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQy9DLENBQUMifQ==
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The Spark release every node of the fleet should run: one row in Cloudly's database, the only
3
+ * source of the release a fresh node's bootstrap installs and an update moves a node to.
4
+ *
5
+ * An administrator names the version and the revision they read; Cloudly reads the SHA-256 of the
6
+ * release's `install.sh` from the release's published `SHA256SUMS.txt`, checks it against the
7
+ * `install.sh` it downloads, and stores the pair. Nobody types a hash. A database without the row
8
+ * holds no target, and every reader refuses by name (`spark-target-unset`) rather than falling
9
+ * back to a release of its own.
10
+ *
11
+ * A target below the release a node runs is admitted: that node reads `ahead` of it, and an update
12
+ * that would move it down is refused (`spark-target-below-node`).
13
+ */
14
+ export interface ISparkFleetTarget {
15
+ /** `MAJOR.MINOR.PATCH`, without `v`. */
16
+ version: string;
17
+ /** The SHA-256 the release's `SHA256SUMS.txt` states for `install.sh`, lowercase hex. */
18
+ installerSha256: string;
19
+ /** Advances by one with every change; the first target is revision 1. */
20
+ revision: number;
21
+ /** Who set it: `admin:<userId>`, or `migration:<step>` for the target a data step seeded. */
22
+ setBy: string;
23
+ setAt: number;
24
+ }
25
+ /**
26
+ * One change of the fleet target, kept in an append-only history: the target as it was set, and
27
+ * the version it replaced (`null` for the first). The history is the audit; nothing else records
28
+ * why a target moved.
29
+ */
30
+ export interface ISparkFleetTargetChange extends ISparkFleetTarget {
31
+ previousVersion: string | null;
32
+ }
33
+ /**
34
+ * One node held at the release it runs: it reads `held` rather than behind or ahead, and an update
35
+ * of it is refused (`spark-target-held`) until the hold is released. There is no per-node or
36
+ * per-cluster target; a hold is the only exception to the one fleet target.
37
+ */
38
+ export interface ISparkNodeTargetHold {
39
+ nodeId: string;
40
+ /** Operator text naming why the node is held. */
41
+ reason: string;
42
+ heldBy: string;
43
+ heldAt: number;
44
+ }
45
+ export declare const sparkFleetTargetContract: Readonly<{
46
+ /** Changes one `getSparkFleetTarget` answers, newest first. */
47
+ readonly historyLimit: 50;
48
+ /** Characters of one hold's reason. */
49
+ readonly maximumHoldReasonLength: 256;
50
+ }>;
51
+ /**
52
+ * Every reason Cloudly answers instead of reading or changing the fleet target or a hold. Each
53
+ * travels by name as a `TypedResponseError`, and a refused request changes nothing.
54
+ */
55
+ export declare const sparkFleetTargetRefusals: readonly ["spark-target-request-invalid", "spark-target-revision-stale", "spark-release-unavailable", "spark-release-checksum-missing", "spark-release-checksum-mismatch", "node-missing"];
56
+ export type TSparkFleetTargetRefusalCode = typeof sparkFleetTargetRefusals[number];
57
+ /**
58
+ * Where one node stands against the fleet target.
59
+ *
60
+ * - `at-target`, `behind`, `ahead`: the Spark bundle version it reports, compared to the target.
61
+ * - `held`: the node is held (`ISparkNodeTargetHold`), whatever it runs.
62
+ * - `unreported`: the node reports no Spark bundle version (it never reported through the node
63
+ * runtime).
64
+ * - `untargeted`: there is no fleet target.
65
+ */
66
+ export type TSparkNodeTargetStanding = 'at-target' | 'behind' | 'ahead' | 'held' | 'unreported' | 'untargeted';
67
+ export declare const readSparkNodeTargetStanding: (argsArg: {
68
+ bundleVersion: string | null;
69
+ target: Pick<ISparkFleetTarget, 'version'> | null;
70
+ held: boolean;
71
+ }) => TSparkNodeTargetStanding;
72
+ /**
73
+ * Validates one `setSparkFleetTarget` input and names every reason it is refused: a release version
74
+ * and the revision of the target the administrator read, `0` while there is none.
75
+ */
76
+ export declare const validateSetSparkFleetTargetInput: (valueArg: unknown, pathArg?: string) => string[];
77
+ /**
78
+ * Validates one hold a request states: `{ reason }` to hold the node, `null` to release it.
79
+ */
80
+ export declare const validateSparkNodeTargetHoldInput: (valueArg: unknown, pathArg?: string) => string[];
@@ -0,0 +1,71 @@
1
+ import { readExactDataRecord } from '../private/dataproperties.js';
2
+ import { compareReleaseVersions, isReleaseVersion, parseReleaseVersion } from './releaseversion.js';
3
+ export const sparkFleetTargetContract = Object.freeze({
4
+ /** Changes one `getSparkFleetTarget` answers, newest first. */
5
+ historyLimit: 50,
6
+ /** Characters of one hold's reason. */
7
+ maximumHoldReasonLength: 256,
8
+ });
9
+ /**
10
+ * Every reason Cloudly answers instead of reading or changing the fleet target or a hold. Each
11
+ * travels by name as a `TypedResponseError`, and a refused request changes nothing.
12
+ */
13
+ export const sparkFleetTargetRefusals = Object.freeze([
14
+ /** The request is not well-formed (`validateSetSparkFleetTargetInput`, `validateSparkNodeTargetHoldInput`). */
15
+ 'spark-target-request-invalid',
16
+ /** The target moved since the revision the request names (`0` while there is none). */
17
+ 'spark-target-revision-stale',
18
+ /** The release, its `SHA256SUMS.txt` or its `install.sh` could not be downloaded. */
19
+ 'spark-release-unavailable',
20
+ /** The release's `SHA256SUMS.txt` states no SHA-256 for `install.sh`. */
21
+ 'spark-release-checksum-missing',
22
+ /** The downloaded `install.sh` does not have the SHA-256 its `SHA256SUMS.txt` states. */
23
+ 'spark-release-checksum-mismatch',
24
+ /** No node of that id exists. */
25
+ 'node-missing',
26
+ ]);
27
+ export const readSparkNodeTargetStanding = (argsArg) => {
28
+ if (argsArg.held)
29
+ return 'held';
30
+ if (argsArg.target === null)
31
+ return 'untargeted';
32
+ const running = parseReleaseVersion(argsArg.bundleVersion);
33
+ const target = parseReleaseVersion(argsArg.target.version);
34
+ if (running === null || target === null)
35
+ return 'unreported';
36
+ const order = compareReleaseVersions(running, target);
37
+ return order === 0 ? 'at-target' : order < 0 ? 'behind' : 'ahead';
38
+ };
39
+ /**
40
+ * Validates one `setSparkFleetTarget` input and names every reason it is refused: a release version
41
+ * and the revision of the target the administrator read, `0` while there is none.
42
+ */
43
+ export const validateSetSparkFleetTargetInput = (valueArg, pathArg = 'spark fleet target input') => {
44
+ const value = readExactDataRecord(valueArg, ['version', 'expectedRevision']);
45
+ if (value === null)
46
+ return [`${pathArg} must use its exact schema`];
47
+ const errors = [];
48
+ if (!isReleaseVersion(value.version))
49
+ errors.push(`${pathArg} version must be a MAJOR.MINOR.PATCH release version`);
50
+ if (!Number.isSafeInteger(value.expectedRevision) || Object.is(value.expectedRevision, -0)
51
+ || value.expectedRevision < 0) {
52
+ errors.push(`${pathArg} expectedRevision must be a non-negative integer`);
53
+ }
54
+ return errors;
55
+ };
56
+ /**
57
+ * Validates one hold a request states: `{ reason }` to hold the node, `null` to release it.
58
+ */
59
+ export const validateSparkNodeTargetHoldInput = (valueArg, pathArg = 'spark node target hold') => {
60
+ if (valueArg === null)
61
+ return [];
62
+ const value = readExactDataRecord(valueArg, ['reason']);
63
+ if (value === null)
64
+ return [`${pathArg} must be null or use its exact schema`];
65
+ return typeof value.reason === 'string' && value.reason.length > 0
66
+ && value.reason.length <= sparkFleetTargetContract.maximumHoldReasonLength
67
+ && /^[\x20-\x7e]+$/.test(value.reason)
68
+ ? []
69
+ : [`${pathArg} reason must be printable text of 1 to ${sparkFleetTargetContract.maximumHoldReasonLength} characters`];
70
+ };
71
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhcmtmbGVldHRhcmdldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvc3BhcmtmbGVldHRhcmdldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUNuRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQWlEcEcsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNwRCwrREFBK0Q7SUFDL0QsWUFBWSxFQUFFLEVBQUU7SUFDaEIsdUNBQXVDO0lBQ3ZDLHVCQUF1QixFQUFFLEdBQUc7Q0FDcEIsQ0FBQyxDQUFDO0FBRVo7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNwRCwrR0FBK0c7SUFDL0csOEJBQThCO0lBQzlCLHVGQUF1RjtJQUN2Riw2QkFBNkI7SUFDN0IscUZBQXFGO0lBQ3JGLDJCQUEyQjtJQUMzQix5RUFBeUU7SUFDekUsZ0NBQWdDO0lBQ2hDLHlGQUF5RjtJQUN6RixpQ0FBaUM7SUFDakMsaUNBQWlDO0lBQ2pDLGNBQWM7Q0FDTixDQUFDLENBQUM7QUFlWixNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FBRyxDQUFDLE9BSTNDLEVBQTRCLEVBQUU7SUFDN0IsSUFBSSxPQUFPLENBQUMsSUFBSTtRQUFFLE9BQU8sTUFBTSxDQUFDO0lBQ2hDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxJQUFJO1FBQUUsT0FBTyxZQUFZLENBQUM7SUFDakQsTUFBTSxPQUFPLEdBQUcsbUJBQW1CLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQzNELE1BQU0sTUFBTSxHQUFHLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDM0QsSUFBSSxPQUFPLEtBQUssSUFBSSxJQUFJLE1BQU0sS0FBSyxJQUFJO1FBQUUsT0FBTyxZQUFZLENBQUM7SUFDN0QsTUFBTSxLQUFLLEdBQUcsc0JBQXNCLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ3RELE9BQU8sS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQztBQUNwRSxDQUFDLENBQUM7QUFFRjs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRyxDQUM5QyxRQUFpQixFQUNqQixPQUFPLEdBQUcsMEJBQTBCLEVBQzFCLEVBQUU7SUFDWixNQUFNLEtBQUssR0FBRyxtQkFBbUIsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxTQUFTLEVBQUUsa0JBQWtCLENBQUMsQ0FBQyxDQUFDO0lBQzdFLElBQUksS0FBSyxLQUFLLElBQUk7UUFBRSxPQUFPLENBQUMsR0FBRyxPQUFPLDRCQUE0QixDQUFDLENBQUM7SUFDcEUsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sc0RBQXNELENBQUMsQ0FBQztJQUNwSCxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxNQUFNLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLENBQUMsQ0FBQztXQUNwRixLQUFLLENBQUMsZ0JBQTJCLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDNUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sa0RBQWtELENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQyxDQUFDO0FBRUY7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRyxDQUM5QyxRQUFpQixFQUNqQixPQUFPLEdBQUcsd0JBQXdCLEVBQ3hCLEVBQUU7SUFDWixJQUFJLFFBQVEsS0FBSyxJQUFJO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDakMsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztJQUN4RCxJQUFJLEtBQUssS0FBSyxJQUFJO1FBQUUsT0FBTyxDQUFDLEdBQUcsT0FBTyx1Q0FBdUMsQ0FBQyxDQUFDO0lBQy9FLE9BQU8sT0FBTyxLQUFLLENBQUMsTUFBTSxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDO1dBQzdELEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBTSxJQUFJLHdCQUF3QixDQUFDLHVCQUF1QjtXQUN2RSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQztRQUN0QyxDQUFDLENBQUMsRUFBRTtRQUNKLENBQUMsQ0FBQyxDQUFDLEdBQUcsT0FBTywwQ0FBMEMsd0JBQXdCLENBQUMsdUJBQXVCLGFBQWEsQ0FBQyxDQUFDO0FBQzFILENBQUMsQ0FBQyJ9