@serve.zone/interfaces 31.4.0 → 31.6.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.
Files changed (39) hide show
  1. package/changelog.md +78 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/backup.d.ts +13 -0
  4. package/dist_ts/data/cluster.d.ts +44 -9
  5. package/dist_ts/data/cluster.js +35 -12
  6. package/dist_ts/data/clusternode.d.ts +49 -0
  7. package/dist_ts/data/clusternode.js +47 -1
  8. package/dist_ts/data/corestore.d.ts +27 -0
  9. package/dist_ts/data/corestore.js +11 -0
  10. package/dist_ts/data/index.d.ts +1 -0
  11. package/dist_ts/data/index.js +2 -1
  12. package/dist_ts/data/isolatedrestore.js +42 -2
  13. package/dist_ts/requests/backup.d.ts +223 -0
  14. package/dist_ts/requests/cluster.d.ts +17 -0
  15. package/dist_ts/requests/corestore.d.ts +57 -0
  16. package/dist_ts/requests/corestore.js +102 -1
  17. package/dist_ts/requests/platform.d.ts +30 -0
  18. package/dist_ts/runtime.corestore.d.ts +7 -10
  19. package/dist_ts/runtime.corestore.js +1 -1
  20. package/dist_ts/runtime.corestore.relay.d.ts +47 -0
  21. package/dist_ts/runtime.corestore.relay.js +57 -0
  22. package/dist_ts/runtime.d.ts +1 -0
  23. package/dist_ts/runtime.js +2 -1
  24. package/package.json +1 -1
  25. package/readme.md +120 -6
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/data/backup.ts +14 -0
  28. package/ts/data/cluster.ts +76 -17
  29. package/ts/data/clusternode.ts +93 -0
  30. package/ts/data/corestore.ts +28 -0
  31. package/ts/data/index.ts +1 -0
  32. package/ts/data/isolatedrestore.ts +53 -1
  33. package/ts/requests/backup.ts +253 -0
  34. package/ts/requests/cluster.ts +21 -0
  35. package/ts/requests/corestore.ts +183 -0
  36. package/ts/requests/platform.ts +36 -0
  37. package/ts/runtime.corestore.relay.ts +111 -0
  38. package/ts/runtime.corestore.ts +7 -8
  39. package/ts/runtime.ts +1 -0
@@ -1010,5 +1010,98 @@ export interface IClusterNode {
1010
1010
  * Debian packages installed on this node
1011
1011
  */
1012
1012
  requiredDebianPackages: string[];
1013
+
1014
+ /**
1015
+ * Where this node's Corestore answers its control API.
1016
+ *
1017
+ * Cloudly owns it and the relay reads it. Absent means this node runs no
1018
+ * Corestore the relay may reach, which is a fact to report rather than a
1019
+ * reason to guess a hostname: the cluster-local alias every component used
1020
+ * while Docker Swarm resolved it is exactly what stops resolving.
1021
+ */
1022
+ corestore?: IClusterNodeCorestore;
1013
1023
  };
1014
1024
  }
1025
+
1026
+ /**
1027
+ * How the cluster relay reaches one node's Corestore.
1028
+ *
1029
+ * Corestore is node-local: the database and the bucket of a workload live on
1030
+ * the node the workload runs on, so one relay serving a whole cluster must be
1031
+ * told, per node, where that node's control API listens. This is control-plane
1032
+ * state, owned by Cloudly and stored in its database like every other field of
1033
+ * the cluster document — never a file, an alias or an environment variable on
1034
+ * the relay.
1035
+ */
1036
+ export interface IClusterNodeCorestore {
1037
+ /**
1038
+ * The canonical origin of the control API, for example
1039
+ * `http://10.20.0.15:3000`.
1040
+ *
1041
+ * `http:` is admitted because Corestore terminates no TLS today — it binds a
1042
+ * plain control port and advertises no transport-encryption guarantee — and
1043
+ * the hop is cluster-private by construction, reachable only from inside the
1044
+ * cluster. `https:` is admitted too, so a deployment that puts TLS in front
1045
+ * of Corestore needs no contract change.
1046
+ *
1047
+ * The endpoint says where, never with what: the bearer control token that
1048
+ * authorizes every call is obtained from Cloudly through
1049
+ * `getCorestoreControlCredentialMaterial`, sealed to the X25519 recipient the
1050
+ * relay enrolled with `beginSecretRecipientEnrollment` and
1051
+ * `completeSecretRecipientEnrollment`. No credential ever travels in this
1052
+ * document, and none is read from the relay's environment.
1053
+ */
1054
+ endpoint: string;
1055
+ }
1056
+
1057
+ const maximumClusterNodeCorestoreEndpointLength = 2048;
1058
+
1059
+ /**
1060
+ * Validates one node's Corestore endpoint and names every reason it is refused.
1061
+ *
1062
+ * A relay that dialled a wrong or ambiguous endpoint would provision a
1063
+ * workload's database on a node that is not the workload's, so the origin must
1064
+ * be exact: a scheme this contract admits, a host, an optional port, and
1065
+ * nothing else — no path, no query, no fragment, no userinfo, no trailing
1066
+ * slash.
1067
+ */
1068
+ export const validateClusterNodeCorestore = (
1069
+ valueArg: unknown,
1070
+ pathArg = 'cluster node corestore',
1071
+ ): string[] => {
1072
+ if (!valueArg
1073
+ || typeof valueArg !== 'object'
1074
+ || Array.isArray(valueArg)
1075
+ || (Object.getPrototypeOf(valueArg) !== Object.prototype
1076
+ && Object.getPrototypeOf(valueArg) !== null)
1077
+ || Object.keys(valueArg as Record<string, unknown>).join(',') !== 'endpoint') {
1078
+ return [`${pathArg} must use its exact schema`];
1079
+ }
1080
+ const endpoint = (valueArg as { endpoint: unknown }).endpoint;
1081
+ if (typeof endpoint !== 'string'
1082
+ || endpoint.length === 0
1083
+ || endpoint.length > maximumClusterNodeCorestoreEndpointLength) {
1084
+ return [`${pathArg} endpoint must be a string of 1 to `
1085
+ + `${maximumClusterNodeCorestoreEndpointLength} characters`];
1086
+ }
1087
+ let parsedEndpoint: URL;
1088
+ try {
1089
+ parsedEndpoint = new URL(endpoint);
1090
+ } catch {
1091
+ return [`${pathArg} endpoint must be a parsable URL`];
1092
+ }
1093
+ const errors: string[] = [];
1094
+ if (parsedEndpoint.protocol !== 'http:' && parsedEndpoint.protocol !== 'https:') {
1095
+ errors.push(`${pathArg} endpoint must use the http or https scheme`);
1096
+ }
1097
+ if (parsedEndpoint.username || parsedEndpoint.password) {
1098
+ errors.push(`${pathArg} endpoint must not carry userinfo`);
1099
+ }
1100
+ if (parsedEndpoint.origin !== endpoint) {
1101
+ errors.push(
1102
+ `${pathArg} endpoint must be the canonical origin ${parsedEndpoint.origin}, `
1103
+ + 'without a path, query, fragment, trailing slash or default port',
1104
+ );
1105
+ }
1106
+ return errors;
1107
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The Corestore shapes the control plane exchanges outside the Node-only
3
+ * `/runtime` export.
4
+ *
5
+ * Only what a browser-visible contract has to name lives here. Everything that
6
+ * needs Corestore's canonical JSON, digests or receipts stays in
7
+ * `runtime.corestore.*`, which imports this declaration rather than repeating
8
+ * it: one structure, one rule set, two entry points.
9
+ */
10
+
11
+ /**
12
+ * The safe reference to an allocation-managed Corestore database.
13
+ *
14
+ * Corestore returns it when a database is allocated and requires the exact same
15
+ * value back on every later operation against that database. It intentionally
16
+ * excludes the NoSQLDB principal, the publication capability, the database URI,
17
+ * the password and every provider-private evidence field, so it can travel
18
+ * through a control plane, be persisted as authority and be quoted in a
19
+ * validation reason without disclosing a credential.
20
+ */
21
+ export interface ICorestoreDatabaseAllocationReference {
22
+ purpose: 'scratch-rehearsal';
23
+ rehearsalId: string;
24
+ allocationId: string;
25
+ generation: number;
26
+ bindingSha256: string;
27
+ receiptSha256: string;
28
+ }
package/ts/data/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './cloudlyconfig.js';
2
2
  export * from './cluster.js';
3
3
  export * from './clusteringress.js';
4
4
  export * from './config.js';
5
+ export * from './corestore.js';
5
6
  export * from './coremail.js';
6
7
  export * from './coremail.runtime.js';
7
8
  export * from './deployment.js';
@@ -3,6 +3,7 @@ import type {
3
3
  IBackupArchiveObject,
4
4
  TIsolatedRestoreResourceMapping,
5
5
  } from './backup.js';
6
+ import type { ICorestoreDatabaseAllocationReference } from './corestore.js';
6
7
  import {
7
8
  canonicalizeStrictJson,
8
9
  createCanonicalJsonSha256Hex,
@@ -614,6 +615,44 @@ const readSafeInteger = (
614
615
  return valueArg;
615
616
  };
616
617
 
618
+ /**
619
+ * Corestore's safe reference to an allocation-managed database, exactly as it
620
+ * issued it.
621
+ *
622
+ * Every field is read here rather than trusted, because this value is bound
623
+ * into the restore grant: a reference the coordinator mistyped would be signed
624
+ * authority for a database nobody meant to replace.
625
+ */
626
+ const readDatabaseAllocationReference = (
627
+ valueArg: unknown,
628
+ fieldNameArg: string,
629
+ ): ICorestoreDatabaseAllocationReference => {
630
+ const value = readRecord(valueArg, fieldNameArg);
631
+ assertExactKeys(
632
+ value,
633
+ [
634
+ 'purpose',
635
+ 'rehearsalId',
636
+ 'allocationId',
637
+ 'generation',
638
+ 'bindingSha256',
639
+ 'receiptSha256',
640
+ ],
641
+ fieldNameArg,
642
+ );
643
+ if (value.purpose !== 'scratch-rehearsal') {
644
+ return fail(`${fieldNameArg}.purpose must be scratch-rehearsal`);
645
+ }
646
+ return {
647
+ purpose: 'scratch-rehearsal',
648
+ rehearsalId: readScopedId(value.rehearsalId, `${fieldNameArg}.rehearsalId`),
649
+ allocationId: readSha256(value.allocationId, `${fieldNameArg}.allocationId`),
650
+ generation: readSafeInteger(value.generation, `${fieldNameArg}.generation`, 1),
651
+ bindingSha256: readSha256(value.bindingSha256, `${fieldNameArg}.bindingSha256`),
652
+ receiptSha256: readSha256(value.receiptSha256, `${fieldNameArg}.receiptSha256`),
653
+ };
654
+ };
655
+
617
656
  const readMountPath = (valueArg: unknown, fieldNameArg: string): string => {
618
657
  const value = readString(
619
658
  valueArg,
@@ -871,7 +910,9 @@ const normalizeMapping = (
871
910
  );
872
911
  assertExactKeys(
873
912
  target,
874
- ['resourceName', 'databaseName'],
913
+ Object.hasOwn(target, 'databaseAllocation')
914
+ ? ['resourceName', 'databaseName', 'databaseAllocation']
915
+ : ['resourceName', 'databaseName'],
875
916
  `${fieldName}.target`,
876
917
  );
877
918
  const sourceResourceName = readResourceName(
@@ -910,6 +951,17 @@ const normalizeMapping = (
910
951
  target: {
911
952
  resourceName: targetResourceName,
912
953
  databaseName: targetDatabaseName,
954
+ // Present exactly when the target is allocation-managed, and therefore
955
+ // part of what the mapping digest binds: Corestore refuses an allocated
956
+ // target whose exact reference the authority did not cover.
957
+ ...(Object.hasOwn(target, 'databaseAllocation')
958
+ ? {
959
+ databaseAllocation: readDatabaseAllocationReference(
960
+ target.databaseAllocation,
961
+ `${fieldName}.target.databaseAllocation`,
962
+ ),
963
+ }
964
+ : {}),
913
965
  },
914
966
  };
915
967
  }
@@ -13,6 +13,7 @@ import type {
13
13
  } from '../data/backup.js';
14
14
  import type { IService } from '../data/service.js';
15
15
  import type { IIdentity, IIdentityCredential } from '../data/user.js';
16
+ import type { ICorestoreDatabaseClosureDescriptor } from '../runtime.corestore.relay.js';
16
17
 
17
18
  export interface IReq_Any_Cloudly_CreateServiceBackup
18
19
  extends plugins.typedrequestInterfaces.implementsTR<
@@ -80,6 +81,15 @@ extends plugins.typedrequestInterfaces.implementsTR<
80
81
  };
81
82
  }
82
83
 
84
+ /**
85
+ * Superseded by `backupClusterService`, which names the node.
86
+ *
87
+ * This method was dispatched at the coreflow that had tagged itself with the
88
+ * node's hostname; a cluster relay authors no tags and serves every node of its
89
+ * cluster, so the node it should act on can no longer be inferred from which
90
+ * connection answered. It is retired together with the tag in the coordinated
91
+ * major.
92
+ */
83
93
  export interface IReq_Cloudly_Coreflow_ExecuteServiceBackup
84
94
  extends plugins.typedrequestInterfaces.implementsTR<
85
95
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -100,6 +110,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
100
110
  };
101
111
  }
102
112
 
113
+ /** Superseded by `restoreClusterService`; see `executeServiceBackup`. */
103
114
  export interface IReq_Cloudly_Coreflow_ExecuteServiceRestore
104
115
  extends plugins.typedrequestInterfaces.implementsTR<
105
116
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -137,6 +148,14 @@ extends plugins.typedrequestInterfaces.implementsTR<
137
148
  };
138
149
  }
139
150
 
151
+ /**
152
+ * Retiring: `contentsBase64` carries a whole archive object inside one JSON
153
+ * envelope, which costs a third of the payload in encoding and bounds a backup
154
+ * by the message ceiling rather than by the transport. `backupServiceDatabaseClosure`
155
+ * streams the same bytes as a VirtualStream. Removal is the coordinated major,
156
+ * once every relay and every Cloudly speak the streamed contract; Corestore
157
+ * already documents its repository-wide archive object routes as legacy.
158
+ */
140
159
  export interface IReq_Coreflow_Cloudly_UploadBackupArchiveObject
141
160
  extends plugins.typedrequestInterfaces.implementsTR<
142
161
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -185,6 +204,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
185
204
  };
186
205
  }
187
206
 
207
+ /** Retiring with `uploadBackupArchiveObject`; `restoreServiceDatabaseClosure` replaces it. */
188
208
  export interface IReq_Coreflow_Cloudly_DownloadBackupArchiveObject
189
209
  extends plugins.typedrequestInterfaces.implementsTR<
190
210
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -202,6 +222,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
202
222
  };
203
223
  }
204
224
 
225
+ /** Superseded by `pruneClusterNodeArchive`; see `executeServiceBackup`. */
205
226
  export interface IReq_Cloudly_Coreflow_PruneNodeArchive
206
227
  extends plugins.typedrequestInterfaces.implementsTR<
207
228
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -218,6 +239,152 @@ extends plugins.typedrequestInterfaces.implementsTR<
218
239
  };
219
240
  }
220
241
 
242
+ /**
243
+ * Backs up one service on one exact node of this cluster.
244
+ *
245
+ * The cluster relay serves every node of its cluster over one session, so the
246
+ * node is a request field: the connection that answers proves which cluster is
247
+ * speaking, never which node is meant. Everything else is what
248
+ * `executeServiceBackup` carried, so a caller moving to this method changes its
249
+ * routing and nothing about its backup.
250
+ */
251
+ export interface IReq_Cloudly_Relay_BackupClusterService
252
+ extends plugins.typedrequestInterfaces.implementsTR<
253
+ plugins.typedrequestInterfaces.ITypedRequest,
254
+ IReq_Cloudly_Relay_BackupClusterService
255
+ > {
256
+ method: 'backupClusterService';
257
+ request: {
258
+ /** The cluster node whose Corestore holds this service's data. */
259
+ nodeId: string;
260
+ backupId: string;
261
+ service: IService;
262
+ tags?: Record<string, string>;
263
+ replication?: {
264
+ enabled: boolean;
265
+ };
266
+ };
267
+ response: {
268
+ snapshots: TBackupSnapshot[];
269
+ replication?: IBackupReplicationResult;
270
+ };
271
+ }
272
+
273
+ /** Restores one service on one exact node of this cluster; see `backupClusterService`. */
274
+ export interface IReq_Cloudly_Relay_RestoreClusterService
275
+ extends plugins.typedrequestInterfaces.implementsTR<
276
+ plugins.typedrequestInterfaces.ITypedRequest,
277
+ IReq_Cloudly_Relay_RestoreClusterService
278
+ > {
279
+ method: 'restoreClusterService';
280
+ request: {
281
+ /** The cluster node whose Corestore receives the restore. */
282
+ nodeId: string;
283
+ backupId: string;
284
+ service: IService;
285
+ snapshots: TBackupSnapshot[];
286
+ clear?: boolean;
287
+ resourceTypes?: Array<TBackupSnapshot['type']>;
288
+ replication?: {
289
+ enabled: boolean;
290
+ };
291
+ };
292
+ response: {
293
+ restored: TBackupSnapshot[];
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Prunes the archive of one exact node of this cluster.
299
+ *
300
+ * `found` stays false when that node has no archive to prune, which is a fact
301
+ * about the node rather than a failure of the relay.
302
+ */
303
+ export interface IReq_Cloudly_Relay_PruneClusterNodeArchive
304
+ extends plugins.typedrequestInterfaces.implementsTR<
305
+ plugins.typedrequestInterfaces.ITypedRequest,
306
+ IReq_Cloudly_Relay_PruneClusterNodeArchive
307
+ > {
308
+ method: 'pruneClusterNodeArchive';
309
+ request: {
310
+ /** The cluster node whose archive is pruned. */
311
+ nodeId: string;
312
+ retention: IArchiveRetentionPolicy;
313
+ dryRun?: boolean;
314
+ };
315
+ response: {
316
+ found: boolean;
317
+ result?: IArchivePruneResult;
318
+ };
319
+ }
320
+
321
+ /**
322
+ * The relay carries one service database closure to Cloudly.
323
+ *
324
+ * Relay-initiated, like every other byte a cluster sends: a cluster is
325
+ * outbound-only, so Cloudly cannot pull from it. The relay exports the closure
326
+ * from its node's Corestore and sends those exact bytes on its own session.
327
+ *
328
+ * `accepted` permits sending; it never confirms storage. The stream's own
329
+ * acceptance receipt, observed through `completion`, is what confirms that
330
+ * Cloudly durably holds the closure — exactly as `pushImageVersion` works.
331
+ *
332
+ * No identity field: the cluster is the one the verified machine identity on
333
+ * the relay's own session names, exactly as `getClusterRouteTable` and
334
+ * `getClusterPlatformDesiredState` work. A request-level identity would be a
335
+ * second authority for the same fact, and the weaker of the two.
336
+ */
337
+ export interface IReq_Relay_Cloudly_BackupServiceDatabaseClosure
338
+ extends plugins.typedrequestInterfaces.implementsTR<
339
+ plugins.typedrequestInterfaces.ITypedRequest,
340
+ IReq_Relay_Cloudly_BackupServiceDatabaseClosure
341
+ > {
342
+ method: 'backupServiceDatabaseClosure';
343
+ request: {
344
+ backupId: string;
345
+ serviceId: string;
346
+ /** The cluster node this closure was exported from. */
347
+ nodeId: string;
348
+ /** Exact bytes and Corestore's receipt for them; validated before any byte moves. */
349
+ descriptor: ICorestoreDatabaseClosureDescriptor;
350
+ /** Requester sends closure bytes; the TypedHandler receives the opposite endpoint. */
351
+ closure: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
352
+ };
353
+ response: {
354
+ accepted: boolean;
355
+ };
356
+ }
357
+
358
+ /**
359
+ * The relay fetches one service database closure back from Cloudly.
360
+ *
361
+ * The descriptor arrives with the stream rather than after it, because
362
+ * Corestore's restore needs the exact byte length and receipt before the first
363
+ * byte is written: the relay opens its Corestore request from this descriptor
364
+ * and only then drains the stream into it.
365
+ *
366
+ * No identity field, for the same reason as `backupServiceDatabaseClosure`: the
367
+ * relay's own session names the cluster.
368
+ */
369
+ export interface IReq_Relay_Cloudly_RestoreServiceDatabaseClosure
370
+ extends plugins.typedrequestInterfaces.implementsTR<
371
+ plugins.typedrequestInterfaces.ITypedRequest,
372
+ IReq_Relay_Cloudly_RestoreServiceDatabaseClosure
373
+ > {
374
+ method: 'restoreServiceDatabaseClosure';
375
+ request: {
376
+ backupId: string;
377
+ serviceId: string;
378
+ /** The cluster node the closure is restored onto. */
379
+ nodeId: string;
380
+ };
381
+ response: {
382
+ descriptor: ICorestoreDatabaseClosureDescriptor;
383
+ /** Requester receives closure bytes; the TypedHandler returns a sender endpoint. */
384
+ closure: plugins.typedrequestInterfaces.TVirtualStream<'receive'>;
385
+ };
386
+ }
387
+
221
388
  /**
222
389
  * Creates a restore in a new non-runnable data-plane scratch namespace.
223
390
  * Cloudly derives clusterId and sourceServiceId from the authoritative backup,
@@ -316,6 +483,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
316
483
  > {
317
484
  method: 'coreflowPrepareIsolatedRestore';
318
485
  request: {
486
+ /**
487
+ * The compact RS256 restore grant Cloudly signed for exactly this
488
+ * operation, carried untouched to Corestore.
489
+ *
490
+ * Corestore accepts no other authority on an isolated-restore route: the
491
+ * grant names the operation, expires within minutes, and binds the cluster,
492
+ * the node, the canonical resource mappings and the archive manifest, so a
493
+ * relay can carry it but can neither widen it nor mint one. Sensitive
494
+ * ephemeral input: exclude the complete request from hooks, logs and
495
+ * journals on every hop.
496
+ */
497
+ restoreGrant: string;
319
498
  clusterId: string;
320
499
  targetNodeName: string;
321
500
  restoreId: string;
@@ -332,6 +511,44 @@ extends plugins.typedrequestInterfaces.implementsTR<
332
511
  };
333
512
  }
334
513
 
514
+ /**
515
+ * Cloudly stages one archive object of an isolated restore on the relay's node.
516
+ *
517
+ * Cloudly holds the source backup, so these bytes travel Cloudly → relay and
518
+ * the requester is the sender. One object per call, as the archive manifest
519
+ * lists it: Corestore writes staged objects under a `write-object` grant, one
520
+ * bounded chunk at a time, and the relay is what turns this stream into those
521
+ * chunks. The object descriptor is the manifest entry itself — path, exact size
522
+ * and digest — so the relay can prove the complete object before it counts as
523
+ * written, and so a replayed stage settles on the same bytes.
524
+ */
525
+ export interface IReq_Cloudly_Relay_StageIsolatedRestoreArchive
526
+ extends plugins.typedrequestInterfaces.implementsTR<
527
+ plugins.typedrequestInterfaces.ITypedRequest,
528
+ IReq_Cloudly_Relay_StageIsolatedRestoreArchive
529
+ > {
530
+ method: 'stageIsolatedRestoreArchive';
531
+ request: {
532
+ /**
533
+ * The compact RS256 restore grant for the `write-object` operation, carried
534
+ * untouched to Corestore. Sensitive ephemeral input, like every other
535
+ * grant on these routes.
536
+ */
537
+ restoreGrant: string;
538
+ clusterId: string;
539
+ targetNodeName: string;
540
+ restoreId: string;
541
+ expectedRevision: number;
542
+ /** The manifest entry these bytes complete: `path`, `size`, `sha256`. */
543
+ object: IBackupArchiveObject;
544
+ /** Requester sends the object bytes; the TypedHandler receives them. */
545
+ archive: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
546
+ };
547
+ response: {
548
+ state: IIsolatedRestoreNodeState;
549
+ };
550
+ }
551
+
335
552
  /** Read-only status for one exact cluster/node/restore routing key. */
336
553
  export interface IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus
337
554
  extends plugins.typedrequestInterfaces.implementsTR<
@@ -340,6 +557,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
340
557
  > {
341
558
  method: 'coreflowGetIsolatedRestoreStatus';
342
559
  request: {
560
+ /**
561
+ * The compact RS256 restore grant Cloudly signed for exactly this
562
+ * operation, carried untouched to Corestore.
563
+ *
564
+ * Corestore accepts no other authority on an isolated-restore route: the
565
+ * grant names the operation, expires within minutes, and binds the cluster,
566
+ * the node, the canonical resource mappings and the archive manifest, so a
567
+ * relay can carry it but can neither widen it nor mint one. Sensitive
568
+ * ephemeral input: exclude the complete request from hooks, logs and
569
+ * journals on every hop.
570
+ */
571
+ restoreGrant: string;
343
572
  clusterId: string;
344
573
  targetNodeName: string;
345
574
  restoreId: string;
@@ -357,6 +586,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
357
586
  > {
358
587
  method: 'coreflowExecuteIsolatedRestore';
359
588
  request: {
589
+ /**
590
+ * The compact RS256 restore grant Cloudly signed for exactly this
591
+ * operation, carried untouched to Corestore.
592
+ *
593
+ * Corestore accepts no other authority on an isolated-restore route: the
594
+ * grant names the operation, expires within minutes, and binds the cluster,
595
+ * the node, the canonical resource mappings and the archive manifest, so a
596
+ * relay can carry it but can neither widen it nor mint one. Sensitive
597
+ * ephemeral input: exclude the complete request from hooks, logs and
598
+ * journals on every hop.
599
+ */
600
+ restoreGrant: string;
360
601
  clusterId: string;
361
602
  targetNodeName: string;
362
603
  restoreId: string;
@@ -378,6 +619,18 @@ extends plugins.typedrequestInterfaces.implementsTR<
378
619
  > {
379
620
  method: 'coreflowCleanupIsolatedRestore';
380
621
  request: {
622
+ /**
623
+ * The compact RS256 restore grant Cloudly signed for exactly this
624
+ * operation, carried untouched to Corestore.
625
+ *
626
+ * Corestore accepts no other authority on an isolated-restore route: the
627
+ * grant names the operation, expires within minutes, and binds the cluster,
628
+ * the node, the canonical resource mappings and the archive manifest, so a
629
+ * relay can carry it but can neither widen it nor mint one. Sensitive
630
+ * ephemeral input: exclude the complete request from hooks, logs and
631
+ * journals on every hop.
632
+ */
633
+ restoreGrant: string;
381
634
  clusterId: string;
382
635
  targetNodeName: string;
383
636
  restoreId: string;
@@ -109,6 +109,27 @@ export interface IReq_Any_Cloudly_DisableClusterRelayV1 extends plugins.typedreq
109
109
  };
110
110
  }
111
111
 
112
+ /**
113
+ * The relay registrations an administrator asks Cloudly about.
114
+ *
115
+ * Read-only: it answers with what Cloudly accepted, plus whether a verified socket carries each
116
+ * relay at this instant. `clusterIds` narrows the answer to the named clusters; omitting it asks
117
+ * for every cluster whose relay ever registered.
118
+ */
119
+ export interface IReq_Any_Cloudly_GetClusterRelayRegistrations extends plugins.typedrequestInterfaces.implementsTR<
120
+ plugins.typedrequestInterfaces.ITypedRequest,
121
+ IReq_Any_Cloudly_GetClusterRelayRegistrations
122
+ > {
123
+ method: 'getClusterRelayRegistrations';
124
+ request: {
125
+ identity: userInterfaces.IIdentity;
126
+ clusterIds?: string[];
127
+ };
128
+ response: {
129
+ registrations: clusterInterfaces.IClusterRelayRegistration[];
130
+ };
131
+ }
132
+
112
133
  /**
113
134
  * The cluster relay tells Cloudly where it listens, over its own authenticated socket.
114
135
  *