@serve.zone/interfaces 32.16.0 → 32.18.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,436 @@
1
+ import type { IFleetCutoverAcceptedEvidenceReference } from './fleetcutover.js';
2
+ import type { TSha256Digest } from './immutableimage.js';
3
+ import { isReleaseVersion } from './releaseversion.js';
4
+ import {
5
+ captureNetwork,
6
+ networkCounter,
7
+ networkDigest,
8
+ networkEqual,
9
+ networkHash,
10
+ networkId,
11
+ networkNodeId,
12
+ networkRecord,
13
+ networkReference,
14
+ networkRevision,
15
+ requireNetwork,
16
+ } from '../private/networkcontract.js';
17
+
18
+ /** The two runtimes a released Spark could be put into by its `mode` configuration key. */
19
+ export const sparkLegacyRuntimeModes = ['cloudly', 'coreflow-node'] as const;
20
+
21
+ export type TSparkLegacyRuntimeMode = typeof sparkLegacyRuntimeModes[number];
22
+
23
+ /**
24
+ * Which Docker services a released mode owns, sorted, so a refusal names a mode instead of
25
+ * restating a set. A record over the mode union: a mode cannot be added without naming its
26
+ * services, and a reader can never meet a mode whose services it does not know.
27
+ */
28
+ export const sparkLegacyRuntimeModeServices = Object.freeze({
29
+ 'cloudly': Object.freeze(['cloudly']),
30
+ 'coreflow-node': Object.freeze(['coreflow', 'corestore', 'coretraffic']),
31
+ } satisfies Record<TSparkLegacyRuntimeMode, readonly string[]>);
32
+
33
+ /**
34
+ * Which verified Spark code is installed on one host and which systemd unit will boot it.
35
+ *
36
+ * Every member is read back from the installed tree and the installed unit file, never from the
37
+ * running process's own build constants: a build can state its own version, it cannot state that
38
+ * the host will start that same code again.
39
+ */
40
+ export interface ISparkNodeBundleIdentity {
41
+ /** The installed bundle's release, exactly as its verified manifest names it. */
42
+ version: string;
43
+ /** The commit that manifest was built from, in the 40-hex form the manifest carries. */
44
+ sourceCommit: string;
45
+ /** The manifest digest the installed tree was verified against, in its bare 64-hex form. */
46
+ manifestSha256: string;
47
+ /** The systemd unit that starts this bundle at boot. */
48
+ unitName: string;
49
+ /** SHA-256 of the exact installed unit-file bytes. */
50
+ unitDigest: TSha256Digest;
51
+ }
52
+
53
+ /**
54
+ * What became of the released configuration source once this node stopped reading it.
55
+ *
56
+ * A tuple beside the union, exactly like `sparkLegacyRuntimeModes`: a reader that has to name the
57
+ * two states — a form, a migration, a refusal message — reads them from the contract instead of
58
+ * restating two string literals the contract could later disagree with.
59
+ */
60
+ export const sparkLegacyConfigurationDispositions = ['retained-inert', 'removed'] as const;
61
+
62
+ export type TSparkLegacyConfigurationDisposition =
63
+ typeof sparkLegacyConfigurationDispositions[number];
64
+
65
+ /**
66
+ * The released key-value configuration source, by digest and disposition only.
67
+ *
68
+ * Never its bytes and never a decoded member: that file carries this node's released bearer and
69
+ * its jump code. `sourceSha256` covers the whole file, whose smallest unguessable part is a
70
+ * 256-bit token, so it identifies the file without stating anything that is in it.
71
+ */
72
+ export interface ISparkLegacyConfigurationState {
73
+ /** The node's own monotonic counter; one accepted local disposition change advances it by one. */
74
+ ownerGeneration: number;
75
+ /** SHA-256 of the exact source bytes this node imported, once, under its fenced read. */
76
+ sourceSha256: string;
77
+ /** On disk and read by nothing, or unlinked. Erasure is not a cutover requirement. */
78
+ disposition: TSparkLegacyConfigurationDisposition;
79
+ }
80
+
81
+ /**
82
+ * What one node states about the released Spark runtime it is replacing, on one observation.
83
+ *
84
+ * Observed values and one statement the owner makes about its own behaviour. It names no node:
85
+ * Cloudly binds it to the reporter it already authenticated, so there is nothing to disagree
86
+ * with. It carries no digest of its own either, because the whole observation is digested by
87
+ * `computeSparkSwarmObservationDigest`.
88
+ */
89
+ export interface ISparkLegacyRuntimeRetirement {
90
+ /** The released mode this process is configured in and no longer serves. */
91
+ refusedMode: TSparkLegacyRuntimeMode;
92
+ bundle: ISparkNodeBundleIdentity;
93
+ configuration: ISparkLegacyConfigurationState;
94
+ }
95
+
96
+ interface ISparkLegacyRetirementReceiptBase {
97
+ id: string;
98
+ generation: number;
99
+ previous: IFleetCutoverAcceptedEvidenceReference | null;
100
+ clusterId: string;
101
+ nodeId: string;
102
+ /** The accepted observation this was derived from; Cloudly read it, the node never named it. */
103
+ observation: IFleetCutoverAcceptedEvidenceReference;
104
+ reporterSessionId: string;
105
+ observationSequence: number;
106
+ acceptedAt: number;
107
+ digest: TSha256Digest;
108
+ }
109
+
110
+ /**
111
+ * Cloudly's accepted record that one node refuses its released owning mode.
112
+ *
113
+ * It is revisioned rather than written once: a node restates the refusal on every observation,
114
+ * and a bundle upgrade changes what the boot path is, which is a new fact about the same node.
115
+ */
116
+ export interface ISparkLegacyModeRefusal extends ISparkLegacyRetirementReceiptBase {
117
+ refusedMode: TSparkLegacyRuntimeMode;
118
+ bundle: ISparkNodeBundleIdentity;
119
+ }
120
+
121
+ /**
122
+ * Cloudly's accepted record of one node's released configuration source at an exact generation.
123
+ *
124
+ * Two counters, deliberately: `generation` is contiguous because it is this record's own
125
+ * revision, while `configuration.ownerGeneration` is the node's and may skip — an observation
126
+ * that never arrived must not look like a skipped revision here.
127
+ */
128
+ export interface ISparkLegacyConfigurationRetirement extends ISparkLegacyRetirementReceiptBase {
129
+ configuration: ISparkLegacyConfigurationState;
130
+ }
131
+
132
+ export const sparkLegacyRetirementContract = Object.freeze({
133
+ modeRefusalDigestDomain: 'serve.zone/spark-legacy-mode-refusal',
134
+ configurationRetirementDigestDomain: 'serve.zone/spark-legacy-configuration-retirement',
135
+ } as const);
136
+
137
+ /** Every reason Cloudly answers instead of advancing a node's legacy retirement state. */
138
+ export const sparkLegacyRetirementRefusals = Object.freeze([
139
+ /** The observed owner generation is below the accepted one. */
140
+ 'legacy-retirement-generation-stale',
141
+ /** The observed owner generation equals the accepted one and states something else. */
142
+ 'legacy-retirement-generation-conflict',
143
+ /** A node imported one source once; another source digest describes another host. */
144
+ 'legacy-retirement-source-changed',
145
+ /** A removed source cannot become a retained one. */
146
+ 'legacy-retirement-disposition-reversed',
147
+ /** The stated bundle is below the release whose refusal this server accepts as real. */
148
+ 'legacy-retirement-bundle-below-minimum',
149
+ ] as const);
150
+
151
+ export type TSparkLegacyRetirementRefusal = typeof sparkLegacyRetirementRefusals[number];
152
+
153
+ /**
154
+ * What the server did with the statement one observation carried, answered on that observation's
155
+ * own accepted receipt.
156
+ *
157
+ * The statement is judged against records, not against the message: a malformed one is already
158
+ * refused by `validateSparkLegacyRuntimeRetirement` before any of this, so everything left is a
159
+ * fact about this node's history. That is why a refusal here does not reject the observation —
160
+ * the node's liveness and census are true whatever its released runtime states — and why an
161
+ * accepted observation is the only place this outcome appears.
162
+ *
163
+ * `generation` is the revision both node-owned records stand at once the observation is accepted:
164
+ * `accepted` advanced it, `restated` is the replay that renewed nothing and left it where it was.
165
+ * A refusal carries no generation, because the counter a refused node has to reconcile with is
166
+ * its own `ownerGeneration`, which it already holds.
167
+ */
168
+ export type TSparkLegacyRetirementOutcome =
169
+ | {
170
+ state: 'accepted';
171
+ /** The revision this observation advanced both records to. */
172
+ generation: number;
173
+ }
174
+ | {
175
+ state: 'restated';
176
+ /** The revision that stands; this observation added nothing to it. */
177
+ generation: number;
178
+ }
179
+ | {
180
+ state: 'refused';
181
+ refusal: TSparkLegacyRetirementRefusal;
182
+ /**
183
+ * Always false, and stated anyway.
184
+ *
185
+ * Every refusal of this family answers one exact statement, so resending it cannot change
186
+ * the answer; a server-side race is refused one level up, as the observation's own
187
+ * `acceptance-race`. It travels on the wire for the reason the rejected receipt carries its
188
+ * own flag: a node that meets a refusal name a later minor added still learns it must stop.
189
+ */
190
+ retryable: false;
191
+ };
192
+
193
+ const fail = (): never => { throw new Error('Spark legacy retirement contract rejected.'); };
194
+ const safe = <T>(operationArg: () => T): T => {
195
+ try { return operationArg(); } catch { return fail(); }
196
+ };
197
+
198
+ const sha256Hex = (valueArg: unknown): string => {
199
+ requireNetwork(typeof valueArg === 'string' && /^[a-f0-9]{64}$/.test(valueArg));
200
+ return valueArg;
201
+ };
202
+
203
+ const sourceCommit = (valueArg: unknown): string => {
204
+ requireNetwork(typeof valueArg === 'string' && /^[a-f0-9]{40}$/.test(valueArg));
205
+ return valueArg;
206
+ };
207
+
208
+ const releaseVersion = (valueArg: unknown): string => {
209
+ requireNetwork(isReleaseVersion(valueArg));
210
+ return valueArg;
211
+ };
212
+
213
+ const legacyRuntimeMode = (valueArg: unknown): TSparkLegacyRuntimeMode => {
214
+ requireNetwork(sparkLegacyRuntimeModes.includes(valueArg as TSparkLegacyRuntimeMode));
215
+ return valueArg as TSparkLegacyRuntimeMode;
216
+ };
217
+
218
+ const configurationDisposition = (
219
+ valueArg: unknown,
220
+ ): TSparkLegacyConfigurationDisposition => {
221
+ requireNetwork(sparkLegacyConfigurationDispositions
222
+ .includes(valueArg as TSparkLegacyConfigurationDisposition));
223
+ return valueArg as TSparkLegacyConfigurationDisposition;
224
+ };
225
+
226
+ const retirementRefusal = (valueArg: unknown): TSparkLegacyRetirementRefusal => {
227
+ requireNetwork(sparkLegacyRetirementRefusals
228
+ .includes(valueArg as TSparkLegacyRetirementRefusal));
229
+ return valueArg as TSparkLegacyRetirementRefusal;
230
+ };
231
+
232
+ /** Structural copy only; whether the tree it describes is installed is the node's to observe. */
233
+ export const snapshotSparkNodeBundleIdentity = (
234
+ valueArg: unknown,
235
+ ): ISparkNodeBundleIdentity => safe(() => {
236
+ const value = networkRecord(captureNetwork(valueArg), [
237
+ 'version', 'sourceCommit', 'manifestSha256', 'unitName', 'unitDigest',
238
+ ]);
239
+ return {
240
+ version: releaseVersion(value.version),
241
+ sourceCommit: sourceCommit(value.sourceCommit),
242
+ manifestSha256: sha256Hex(value.manifestSha256),
243
+ unitName: networkId(value.unitName),
244
+ unitDigest: networkDigest(value.unitDigest),
245
+ };
246
+ });
247
+
248
+ export const snapshotSparkLegacyConfigurationState = (
249
+ valueArg: unknown,
250
+ ): ISparkLegacyConfigurationState => safe(() => {
251
+ const value = networkRecord(captureNetwork(valueArg), [
252
+ 'ownerGeneration', 'sourceSha256', 'disposition',
253
+ ]);
254
+ return {
255
+ ownerGeneration: networkCounter(value.ownerGeneration),
256
+ sourceSha256: sha256Hex(value.sourceSha256),
257
+ disposition: configurationDisposition(value.disposition),
258
+ };
259
+ });
260
+
261
+ export const snapshotSparkLegacyRuntimeRetirement = (
262
+ valueArg: unknown,
263
+ ): ISparkLegacyRuntimeRetirement => safe(() => {
264
+ const value = networkRecord(captureNetwork(valueArg), [
265
+ 'refusedMode', 'bundle', 'configuration',
266
+ ]);
267
+ return {
268
+ refusedMode: legacyRuntimeMode(value.refusedMode),
269
+ bundle: snapshotSparkNodeBundleIdentity(value.bundle),
270
+ configuration: snapshotSparkLegacyConfigurationState(value.configuration),
271
+ };
272
+ });
273
+
274
+ /**
275
+ * Synchronous because the member carries no digest: the observation that transports it is
276
+ * digested as a whole, so a second domain here would only be a second thing to keep in step.
277
+ */
278
+ export const validateSparkLegacyRuntimeRetirement = (valueArg: unknown): string[] => {
279
+ try {
280
+ snapshotSparkLegacyRuntimeRetirement(valueArg);
281
+ return [];
282
+ } catch { return ['Spark legacy runtime retirement must use its exact canonical schema']; }
283
+ };
284
+
285
+ /** Exact-key per state, like every other shape here: an outcome states one answer and no other. */
286
+ export const snapshotSparkLegacyRetirementOutcome = (
287
+ valueArg: unknown,
288
+ ): TSparkLegacyRetirementOutcome => safe(() => {
289
+ const captured = captureNetwork(valueArg);
290
+ requireNetwork(captured !== null && typeof captured === 'object');
291
+ const state = (captured as Record<string, unknown>).state;
292
+ requireNetwork(state === 'accepted' || state === 'restated' || state === 'refused');
293
+ if (state === 'refused') {
294
+ const value = networkRecord(captured, ['state', 'refusal', 'retryable']);
295
+ requireNetwork(value.retryable === false);
296
+ return { state, refusal: retirementRefusal(value.refusal), retryable: false };
297
+ }
298
+ const value = networkRecord(captured, ['state', 'generation']);
299
+ return { state, generation: networkCounter(value.generation) };
300
+ });
301
+
302
+ /** Synchronous for the same reason: the receipt that carries it is what a reader binds. */
303
+ export const validateSparkLegacyRetirementOutcome = (valueArg: unknown): string[] => {
304
+ try {
305
+ snapshotSparkLegacyRetirementOutcome(valueArg);
306
+ return [];
307
+ } catch { return ['Spark legacy retirement outcome must use its exact canonical schema']; }
308
+ };
309
+
310
+ /** The revision rule every accepted receipt of this contract obeys, read once for both of them. */
311
+ const retirementReceiptBase = (
312
+ valueArg: unknown,
313
+ ownKeysArg: string[],
314
+ ): { base: ISparkLegacyRetirementReceiptBase; record: Record<string, unknown> } => {
315
+ const value = networkRecord(captureNetwork(valueArg), [
316
+ 'id', 'generation', 'previous', 'clusterId', 'nodeId', 'observation', 'reporterSessionId',
317
+ 'observationSequence', 'acceptedAt', 'digest',
318
+ ...ownKeysArg,
319
+ ]);
320
+ const { id, generation, previous } = networkRevision(value);
321
+ return {
322
+ base: {
323
+ id,
324
+ generation,
325
+ previous,
326
+ clusterId: networkId(value.clusterId),
327
+ nodeId: networkNodeId(value.nodeId),
328
+ observation: networkReference(value.observation),
329
+ reporterSessionId: networkId(value.reporterSessionId),
330
+ observationSequence: networkCounter(value.observationSequence),
331
+ acceptedAt: networkCounter(value.acceptedAt),
332
+ digest: networkDigest(value.digest),
333
+ },
334
+ record: value,
335
+ };
336
+ };
337
+
338
+ export const snapshotSparkLegacyModeRefusal = (
339
+ valueArg: unknown,
340
+ ): ISparkLegacyModeRefusal => safe(() => {
341
+ const { base, record } = retirementReceiptBase(valueArg, ['refusedMode', 'bundle']);
342
+ return {
343
+ ...base,
344
+ refusedMode: legacyRuntimeMode(record.refusedMode),
345
+ bundle: snapshotSparkNodeBundleIdentity(record.bundle),
346
+ };
347
+ });
348
+
349
+ export const computeSparkLegacyModeRefusalDigest = async (
350
+ valueArg: unknown,
351
+ ): Promise<TSha256Digest> => networkHash(
352
+ sparkLegacyRetirementContract.modeRefusalDigestDomain,
353
+ snapshotSparkLegacyModeRefusal(valueArg),
354
+ );
355
+
356
+ export const validateSparkLegacyModeRefusal = async (valueArg: unknown): Promise<string[]> => {
357
+ try {
358
+ const value = snapshotSparkLegacyModeRefusal(valueArg);
359
+ return value.digest === await computeSparkLegacyModeRefusalDigest(value)
360
+ ? [] : ['Spark legacy mode refusal digest does not match'];
361
+ } catch { return ['Spark legacy mode refusal must use its exact canonical schema']; }
362
+ };
363
+
364
+ export const snapshotSparkLegacyConfigurationRetirement = (
365
+ valueArg: unknown,
366
+ ): ISparkLegacyConfigurationRetirement => safe(() => {
367
+ const { base, record } = retirementReceiptBase(valueArg, ['configuration']);
368
+ return { ...base, configuration: snapshotSparkLegacyConfigurationState(record.configuration) };
369
+ });
370
+
371
+ export const computeSparkLegacyConfigurationRetirementDigest = async (
372
+ valueArg: unknown,
373
+ ): Promise<TSha256Digest> => networkHash(
374
+ sparkLegacyRetirementContract.configurationRetirementDigestDomain,
375
+ snapshotSparkLegacyConfigurationRetirement(valueArg),
376
+ );
377
+
378
+ export const validateSparkLegacyConfigurationRetirement = async (
379
+ valueArg: unknown,
380
+ ): Promise<string[]> => {
381
+ try {
382
+ const value = snapshotSparkLegacyConfigurationRetirement(valueArg);
383
+ return value.digest === await computeSparkLegacyConfigurationRetirementDigest(value)
384
+ ? [] : ['Spark legacy configuration retirement digest does not match'];
385
+ } catch {
386
+ return ['Spark legacy configuration retirement must use its exact canonical schema'];
387
+ }
388
+ };
389
+
390
+ /**
391
+ * Whether one observed configuration state is a legal successor of the accepted one.
392
+ *
393
+ * `null` is a node's first accepted state. A replay states the accepted generation again and is
394
+ * therefore not an advance: it renews nothing, which is what keeps `acceptedAt` and the named
395
+ * observation from aging forward on a node that has stopped changing.
396
+ */
397
+ export const advancesSparkLegacyConfigurationState = (
398
+ currentArg: ISparkLegacyConfigurationState | null,
399
+ nextArg: ISparkLegacyConfigurationState,
400
+ ): boolean => {
401
+ const next = snapshotSparkLegacyConfigurationState(nextArg);
402
+ if (currentArg === null) return true;
403
+ if (next.sourceSha256 !== currentArg.sourceSha256) return false;
404
+ if (next.ownerGeneration <= currentArg.ownerGeneration) return false;
405
+ return !(currentArg.disposition === 'removed' && next.disposition !== 'removed');
406
+ };
407
+
408
+ /**
409
+ * Joins the two accepted receipts a Coreflow retirement cites to the one observation they were
410
+ * derived from.
411
+ *
412
+ * Content join only. Whether each receipt is its node's current revision, and whether the
413
+ * observation it names is still fresh, remain Cloudly's to decide against its own records.
414
+ */
415
+ export const bindSparkLegacyRetirement = async (
416
+ modeRefusalArg: unknown,
417
+ configurationRetirementArg: unknown,
418
+ ): Promise<{
419
+ modeRefusal: ISparkLegacyModeRefusal;
420
+ configurationRetirement: ISparkLegacyConfigurationRetirement;
421
+ }> => {
422
+ try {
423
+ const modeRefusal = snapshotSparkLegacyModeRefusal(modeRefusalArg);
424
+ const configurationRetirement = snapshotSparkLegacyConfigurationRetirement(
425
+ configurationRetirementArg,
426
+ );
427
+ if ((await validateSparkLegacyModeRefusal(modeRefusal)).length
428
+ || (await validateSparkLegacyConfigurationRetirement(configurationRetirement)).length) fail();
429
+ requireNetwork(modeRefusal.clusterId === configurationRetirement.clusterId
430
+ && modeRefusal.nodeId === configurationRetirement.nodeId
431
+ && modeRefusal.reporterSessionId === configurationRetirement.reporterSessionId
432
+ && modeRefusal.observationSequence === configurationRetirement.observationSequence
433
+ && networkEqual(modeRefusal.observation, configurationRetirement.observation));
434
+ return { modeRefusal, configurationRetirement };
435
+ } catch { return fail(); }
436
+ };
@@ -1,4 +1,4 @@
1
- import { isReleaseVersion } from '../private/releaseversion.js';
1
+ import { isReleaseVersion } from './releaseversion.js';
2
2
  import { readExactDataRecord } from '../private/dataproperties.js';
3
3
  import { isClusterNodeId } from '../private/nodeidentifier.js';
4
4
  import { protocolRefusalHttpStatus, readProtocolOffer, type IProtocolOffer } from '../protocol.js';
package/ts/protocol.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  maximumReleaseVersionLength,
9
9
  parseReleaseVersion,
10
10
  type IReleaseVersion,
11
- } from './private/releaseversion.js';
11
+ } from './data/releaseversion.js';
12
12
 
13
13
  /**
14
14
  * The contract this build speaks: the installed `@serve.zone/interfaces` release.
@@ -1,5 +1,6 @@
1
1
  import * as userInterfaces from '../data/user.js';
2
2
  import * as clusterInterfaces from '../data/cluster.js';
3
+ import type * as relayCredentialInterfaces from '../data/clusterrelaycredential.js';
3
4
  import type * as clusterVpnInterfaces from '../data/clustervpn.js';
4
5
  import type { IClusterRuntime, TClusterRuntimeBlocker } from '../data/clusterruntime.js';
5
6
  import type { IServiceRuntimeAssignmentView } from '../data/serviceruntime.js';
@@ -150,6 +151,74 @@ export interface IReq_Relay_Cloudly_RegisterClusterRelay extends plugins.typedre
150
151
  response: clusterInterfaces.IRegisterClusterRelayResponse;
151
152
  }
152
153
 
154
+ /**
155
+ * Mints a cluster's relay credential, or rotates it at its exact current generation.
156
+ *
157
+ * Only the verified credential is read from the caller: a credential mutation trusts no identity
158
+ * member a caller states about itself. The bearer is never in the answer; it is delivered as the
159
+ * encrypted secret version `credential.delivery` names. The first mint also records the shared
160
+ * authority the cluster still carries, so a replacement never exists without a named predecessor.
161
+ * Cloudly refuses with one of `data.clusterRelayCredentialRefusals`.
162
+ */
163
+ export interface IReq_Admin_Cloudly_MutateClusterRelayCredential
164
+ extends plugins.typedrequestInterfaces.implementsTR<
165
+ plugins.typedrequestInterfaces.ITypedRequest,
166
+ IReq_Admin_Cloudly_MutateClusterRelayCredential
167
+ > {
168
+ method: 'mutateClusterRelayCredential';
169
+ request: {
170
+ identity: userInterfaces.IIdentityCredential;
171
+ mutation: relayCredentialInterfaces.TClusterRelayCredentialMutation;
172
+ };
173
+ response: relayCredentialInterfaces.IClusterRelayCredentialMutationResult;
174
+ }
175
+
176
+ /**
177
+ * One cluster's relay authority chain as Cloudly records it: the relay credential, the shared
178
+ * authority it replaces and the current accepted registration, each `null` while no record exists.
179
+ *
180
+ * Liveness is not here. It is a fact of a socket, which `getClusterRelayRegistrations` answers and
181
+ * which Cloudly checks for itself before it accepts a retirement.
182
+ */
183
+ export interface IReq_Any_Cloudly_GetClusterRelayCredential
184
+ extends plugins.typedrequestInterfaces.implementsTR<
185
+ plugins.typedrequestInterfaces.ITypedRequest,
186
+ IReq_Any_Cloudly_GetClusterRelayCredential
187
+ > {
188
+ method: 'getClusterRelayCredential';
189
+ request: {
190
+ identity: userInterfaces.IIdentityCredential;
191
+ clusterId: string;
192
+ };
193
+ response: {
194
+ credential: relayCredentialInterfaces.IClusterRelayCredential | null;
195
+ legacy: relayCredentialInterfaces.IClusterLegacyRelayCredential | null;
196
+ registration: clusterInterfaces.IClusterRelayAcceptedRegistration | null;
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Revokes a cluster's shared pre-relay authority, which is the last step of replacing it.
202
+ *
203
+ * Its own request rather than a mutation of the relay credential, because it is the one step that
204
+ * removes authority: Cloudly accepts it only while the named replacement is the current relay
205
+ * credential, the named registration is current and was made under that replacement, and a
206
+ * verified socket carries that registration. Every other answer is one of
207
+ * `data.clusterRelayCredentialRefusals`.
208
+ */
209
+ export interface IReq_Admin_Cloudly_RetireLegacyClusterRelayCredential
210
+ extends plugins.typedrequestInterfaces.implementsTR<
211
+ plugins.typedrequestInterfaces.ITypedRequest,
212
+ IReq_Admin_Cloudly_RetireLegacyClusterRelayCredential
213
+ > {
214
+ method: 'retireLegacyClusterRelayCredential';
215
+ request: {
216
+ identity: userInterfaces.IIdentityCredential;
217
+ retirement: relayCredentialInterfaces.IClusterLegacyRelayCredentialRetirementRequest;
218
+ };
219
+ response: relayCredentialInterfaces.IClusterLegacyRelayCredentialRetirementResult;
220
+ }
221
+
153
222
  /**
154
223
  * Cloudly hands the cluster relay the DNS lease renewals it has signed ahead of time for one node.
155
224
  *
File without changes
File without changes