@serve.zone/interfaces 31.9.0 → 32.0.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 (204) hide show
  1. package/.smartconfig.json +1 -0
  2. package/changelog.md +467 -0
  3. package/dist_ts/00_commitinfo_data.js +3 -3
  4. package/dist_ts/appstore/types.d.ts +4 -6
  5. package/dist_ts/appstore/types.js +5 -5
  6. package/dist_ts/data/backup.d.ts +3 -26
  7. package/dist_ts/data/cluster.d.ts +36 -11
  8. package/dist_ts/data/cluster.js +33 -22
  9. package/dist_ts/data/clusteringress.d.ts +8 -0
  10. package/dist_ts/data/clusteringress.js +13 -8
  11. package/dist_ts/data/clusternode.d.ts +121 -74
  12. package/dist_ts/data/clusternode.js +109 -132
  13. package/dist_ts/data/coremail.d.ts +3 -7
  14. package/dist_ts/data/coremail.js +2 -2
  15. package/dist_ts/data/coremail.runtime.d.ts +0 -1
  16. package/dist_ts/data/coremail.runtime.js +5 -22
  17. package/dist_ts/data/deploymentadoption.d.ts +0 -2
  18. package/dist_ts/data/deploymentadoption.js +40 -6
  19. package/dist_ts/data/deploymentoperation.js +16 -3
  20. package/dist_ts/data/deploymentpreflight.d.ts +0 -2
  21. package/dist_ts/data/deploymentpreflight.js +1 -1
  22. package/dist_ts/data/immutableimage.d.ts +38 -21
  23. package/dist_ts/data/immutableimage.js +45 -5
  24. package/dist_ts/data/index.d.ts +1 -1
  25. package/dist_ts/data/isolatedrestore.d.ts +139 -21
  26. package/dist_ts/data/isolatedrestore.golden.d.ts +0 -1
  27. package/dist_ts/data/isolatedrestore.golden.js +6 -9
  28. package/dist_ts/data/isolatedrestore.js +176 -40
  29. package/dist_ts/data/mail.d.ts +1 -1
  30. package/dist_ts/data/networkaddressplan.d.ts +8 -8
  31. package/dist_ts/data/networkaddressplan.js +4 -4
  32. package/dist_ts/data/networkauthority.d.ts +20 -22
  33. package/dist_ts/data/networkauthority.js +26 -28
  34. package/dist_ts/data/networkcredential.d.ts +10 -10
  35. package/dist_ts/data/networkcredential.js +16 -16
  36. package/dist_ts/data/networkdelivery.d.ts +18 -18
  37. package/dist_ts/data/networkdelivery.js +16 -16
  38. package/dist_ts/data/networklease.d.ts +18 -7
  39. package/dist_ts/data/networklease.js +29 -7
  40. package/dist_ts/data/networknode.d.ts +2 -2
  41. package/dist_ts/data/networkprojection.d.ts +59 -60
  42. package/dist_ts/data/networkprojection.js +53 -57
  43. package/dist_ts/data/networkprotection.d.ts +27 -28
  44. package/dist_ts/data/networkprotection.js +36 -36
  45. package/dist_ts/data/networkrouter.d.ts +11 -11
  46. package/dist_ts/data/networkrouter.js +1 -1
  47. package/dist_ts/data/networksigning.d.ts +16 -18
  48. package/dist_ts/data/networksigning.js +20 -21
  49. package/dist_ts/data/nodecredential.d.ts +4 -6
  50. package/dist_ts/data/nodeenrollment.d.ts +35 -40
  51. package/dist_ts/data/nodeenrollment.js +57 -72
  52. package/dist_ts/data/privatenetwork.d.ts +21 -24
  53. package/dist_ts/data/privatenetwork.js +39 -54
  54. package/dist_ts/data/runtimeassignment.d.ts +50 -34
  55. package/dist_ts/data/runtimeassignment.js +79 -45
  56. package/dist_ts/data/runtimeconfig.d.ts +21 -22
  57. package/dist_ts/data/runtimeconfig.js +24 -24
  58. package/dist_ts/data/runtimesession.d.ts +100 -62
  59. package/dist_ts/data/runtimesession.js +98 -79
  60. package/dist_ts/data/runtimeterminalreceipt.d.ts +28 -29
  61. package/dist_ts/data/runtimeterminalreceipt.js +42 -42
  62. package/dist_ts/data/secret.d.ts +18 -25
  63. package/dist_ts/data/secret.js +12 -52
  64. package/dist_ts/data/servicecredential.d.ts +14 -16
  65. package/dist_ts/data/servicecredential.js +17 -28
  66. package/dist_ts/data/serviceruntime.d.ts +7 -7
  67. package/dist_ts/data/serviceruntime.js +4 -4
  68. package/dist_ts/data/sparknodereport.d.ts +27 -14
  69. package/dist_ts/data/sparknodereport.js +35 -28
  70. package/dist_ts/data/webpush.d.ts +0 -1
  71. package/dist_ts/index.d.ts +2 -1
  72. package/dist_ts/index.js +3 -2
  73. package/dist_ts/platform/objectstorageretention.d.ts +21 -24
  74. package/dist_ts/platform/objectstorageretention.js +6 -18
  75. package/dist_ts/platform/storage.d.ts +1 -3
  76. package/dist_ts/platform/storagemigration.d.ts +0 -7
  77. package/dist_ts/platform/storagemigration.golden.d.ts +0 -1
  78. package/dist_ts/platform/storagemigration.golden.js +8 -12
  79. package/dist_ts/platform/storagemigration.js +6 -51
  80. package/dist_ts/platform/types.d.ts +2 -2
  81. package/dist_ts/private/canonicaljson.d.ts +9 -1
  82. package/dist_ts/private/canonicaljson.js +14 -2
  83. package/dist_ts/private/dataproperties.d.ts +62 -0
  84. package/dist_ts/private/dataproperties.js +117 -0
  85. package/dist_ts/private/networkcontract.d.ts +10 -10
  86. package/dist_ts/private/networkcontract.js +1 -1
  87. package/dist_ts/private/releaseversion.d.ts +37 -0
  88. package/dist_ts/private/releaseversion.js +48 -0
  89. package/dist_ts/private/runtimepolicy.d.ts +2 -2
  90. package/dist_ts/private/runtimepolicy.js +1 -1
  91. package/dist_ts/private/tokenlist.d.ts +9 -0
  92. package/dist_ts/private/tokenlist.js +23 -0
  93. package/dist_ts/protocol.d.ts +137 -0
  94. package/dist_ts/protocol.js +255 -0
  95. package/dist_ts/requests/admin.d.ts +8 -8
  96. package/dist_ts/requests/backup.d.ts +102 -112
  97. package/dist_ts/requests/cluster.d.ts +10 -10
  98. package/dist_ts/requests/clusteringress.d.ts +9 -0
  99. package/dist_ts/requests/clusteringress.js +1 -1
  100. package/dist_ts/requests/config.d.ts +3 -3
  101. package/dist_ts/requests/corestore.d.ts +0 -3
  102. package/dist_ts/requests/corestore.js +5 -9
  103. package/dist_ts/requests/network.d.ts +30 -30
  104. package/dist_ts/requests/node.d.ts +15 -28
  105. package/dist_ts/requests/pallet.d.ts +13 -13
  106. package/dist_ts/requests/runtimesession.d.ts +53 -41
  107. package/dist_ts/requests/secret.d.ts +4 -5
  108. package/dist_ts/requests/secret.js +1 -5
  109. package/dist_ts/runtime.corestore.d.ts +56 -74
  110. package/dist_ts/runtime.corestore.databaseexportpackage.d.ts +30 -36
  111. package/dist_ts/runtime.corestore.databaseexportpackage.golden.d.ts +11 -12
  112. package/dist_ts/runtime.corestore.databaseexportpackage.golden.js +54 -71
  113. package/dist_ts/runtime.corestore.databaseexportpackage.js +9 -25
  114. package/dist_ts/runtime.corestore.js +35 -135
  115. package/dist_ts/runtime.corestore.relay.js +2 -7
  116. package/dist_ts/runtime.d.ts +38 -48
  117. package/dist_ts/runtime.js +20 -77
  118. package/dist_ts/runtime.networkprojection.d.ts +4 -4
  119. package/dist_ts/runtime.networkprojection.js +18 -18
  120. package/dist_ts/runtime.workloadinit.d.ts +23 -24
  121. package/dist_ts/runtime.workloadinit.js +6 -13
  122. package/package.json +2 -2
  123. package/readme.md +421 -270
  124. package/ts/00_commitinfo_data.ts +3 -3
  125. package/ts/appstore/types.ts +4 -6
  126. package/ts/data/backup.ts +3 -38
  127. package/ts/data/cluster.ts +51 -34
  128. package/ts/data/clusteringress.ts +12 -12
  129. package/ts/data/clusternode.ts +204 -232
  130. package/ts/data/coremail.runtime.ts +4 -21
  131. package/ts/data/coremail.ts +3 -7
  132. package/ts/data/deploymentadoption.ts +46 -4
  133. package/ts/data/deploymentoperation.ts +16 -1
  134. package/ts/data/deploymentpreflight.ts +0 -2
  135. package/ts/data/immutableimage.ts +71 -24
  136. package/ts/data/index.ts +1 -1
  137. package/ts/data/isolatedrestore.golden.ts +5 -9
  138. package/ts/data/isolatedrestore.ts +339 -54
  139. package/ts/data/mail.ts +1 -1
  140. package/ts/data/networkaddressplan.ts +13 -13
  141. package/ts/data/networkauthority.ts +42 -46
  142. package/ts/data/networkcredential.ts +24 -24
  143. package/ts/data/networkdelivery.ts +27 -27
  144. package/ts/data/networklease.ts +40 -13
  145. package/ts/data/networknode.ts +2 -2
  146. package/ts/data/networkprojection.ts +119 -124
  147. package/ts/data/networkprotection.ts +54 -55
  148. package/ts/data/networkrouter.ts +14 -14
  149. package/ts/data/networksigning.ts +29 -32
  150. package/ts/data/nodecredential.ts +4 -6
  151. package/ts/data/nodeenrollment.ts +96 -114
  152. package/ts/data/privatenetwork.ts +58 -74
  153. package/ts/data/runtimeassignment.ts +143 -94
  154. package/ts/data/runtimeconfig.ts +45 -46
  155. package/ts/data/runtimesession.ts +185 -138
  156. package/ts/data/runtimeterminalreceipt.ts +72 -73
  157. package/ts/data/secret.ts +28 -76
  158. package/ts/data/servicecredential.ts +33 -43
  159. package/ts/data/serviceruntime.ts +11 -11
  160. package/ts/data/sparknodereport.ts +48 -36
  161. package/ts/data/webpush.ts +0 -1
  162. package/ts/index.ts +2 -0
  163. package/ts/platform/objectstorageretention.ts +36 -55
  164. package/ts/platform/storage.ts +1 -3
  165. package/ts/platform/storagemigration.golden.ts +7 -11
  166. package/ts/platform/storagemigration.ts +5 -55
  167. package/ts/platform/types.ts +2 -2
  168. package/ts/private/canonicaljson.ts +13 -1
  169. package/ts/private/dataproperties.ts +139 -0
  170. package/ts/private/networkcontract.ts +9 -9
  171. package/ts/private/releaseversion.ts +70 -0
  172. package/ts/private/runtimepolicy.ts +2 -2
  173. package/ts/private/tokenlist.ts +30 -0
  174. package/ts/protocol.ts +364 -0
  175. package/ts/requests/admin.ts +11 -11
  176. package/ts/requests/backup.ts +112 -117
  177. package/ts/requests/cluster.ts +13 -13
  178. package/ts/requests/clusteringress.ts +9 -0
  179. package/ts/requests/config.ts +3 -3
  180. package/ts/requests/corestore.ts +4 -16
  181. package/ts/requests/network.ts +40 -40
  182. package/ts/requests/node.ts +21 -38
  183. package/ts/requests/pallet.ts +17 -17
  184. package/ts/requests/runtimesession.ts +63 -51
  185. package/ts/requests/secret.ts +5 -10
  186. package/ts/runtime.corestore.databaseexportpackage.golden.ts +77 -95
  187. package/ts/runtime.corestore.databaseexportpackage.ts +51 -73
  188. package/ts/runtime.corestore.relay.ts +1 -10
  189. package/ts/runtime.corestore.ts +118 -268
  190. package/ts/runtime.networkprojection.ts +30 -30
  191. package/ts/runtime.ts +83 -158
  192. package/ts/runtime.workloadinit.ts +36 -48
  193. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.d.ts +0 -902
  194. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.d.ts +0 -35
  195. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.js +0 -315
  196. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.js +0 -3315
  197. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.d.ts +0 -291
  198. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.d.ts +0 -30
  199. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.js +0 -408
  200. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.js +0 -1126
  201. package/ts/runtime.cloudlylegacydeploymentsettlement.golden.ts +0 -421
  202. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.ts +0 -510
  203. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.ts +0 -2470
  204. package/ts/runtime.cloudlylegacydeploymentsettlement.ts +0 -6630
@@ -2,7 +2,7 @@
2
2
  * autocreated commitinfo by @push.rocks/commitinfo
3
3
  */
4
4
  export const commitinfo = {
5
- name: '@serve.zone/interfaces',
6
- version: '31.9.0',
7
- description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
5
+ name: "@serve.zone/interfaces",
6
+ version: "32.0.0",
7
+ description: "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem."
8
8
  }
@@ -57,10 +57,10 @@ export type TAppStoreTrackingMode = 'tag' | 'digest';
57
57
  export type TAppStoreUpgradeStrategy = 'semver' | 'branch' | 'dockerDigest';
58
58
 
59
59
  export const appStoreStorageFeatureIds = {
60
- bindingsV2: 'storage.bindings.v2',
61
- filesystemV1: 'storage.filesystem.v1',
62
- objectStorageV2: 'storage.object-storage.v2',
63
- objectStorageFileV2: 'storage.object-storage.file.v2',
60
+ bindings: 'storage.bindings',
61
+ filesystem: 'storage.filesystem',
62
+ objectStorage: 'storage.object-storage',
63
+ objectStorageFile: 'storage.object-storage.file',
64
64
  } as const;
65
65
 
66
66
  export type TAppStoreStorageFeatureId =
@@ -333,7 +333,6 @@ export interface IAppStoreApp {
333
333
  }
334
334
 
335
335
  export interface IAppStoreIndex {
336
- schemaVersion: number;
337
336
  updatedAt: string;
338
337
  resolvedAt?: string;
339
338
  apps: IAppStoreApp[];
@@ -423,7 +422,6 @@ export interface IServezoneAppStoreVersion extends IAppStoreVersionConfig {
423
422
  }
424
423
 
425
424
  export interface IServezoneAppStoreManifest {
426
- schemaVersion: number;
427
425
  app: IServezoneAppStoreAppInfo;
428
426
  latestVersion?: string;
429
427
  channel?: string;
package/ts/data/backup.ts CHANGED
@@ -56,7 +56,6 @@ export interface IBackupArchiveObject {
56
56
  }
57
57
 
58
58
  export interface IBackupArchiveManifest {
59
- version: 1;
60
59
  backupId: string;
61
60
  createdAt: number;
62
61
  objects: IBackupArchiveObject[];
@@ -362,43 +361,6 @@ export interface IIsolatedRestoreSourceBackupHold {
362
361
  releaseReason?: string;
363
362
  }
364
363
 
365
- export type TIsolatedRestoreNodeStatus =
366
- | 'preparing'
367
- | 'prepared'
368
- | 'staging'
369
- | 'restoring'
370
- | 'verifying'
371
- | 'ready'
372
- | 'failed'
373
- | 'cleaning'
374
- | 'cleaned'
375
- | 'cleanup-failed';
376
-
377
- /**
378
- * Data-plane state for one exact cluster/node/restore routing key. `revision`
379
- * advances after every mutation and is the compare-and-swap value used by
380
- * prepare, execute, and cleanup requests.
381
- */
382
- export interface IIsolatedRestoreNodeState {
383
- restoreId: string;
384
- clusterId: string;
385
- targetNodeName: string;
386
- sourceBackupId: string;
387
- sourceServiceId: string;
388
- scratchNamespaceId: string;
389
- stagingArchiveId: string;
390
- revision: number;
391
- status: TIsolatedRestoreNodeStatus;
392
- completedResourceMappingIds: string[];
393
- verification: IIsolatedRestoreVerification;
394
- cleanup?: IIsolatedRestoreCleanupProgress;
395
- preparedAt?: number;
396
- readyAt?: number;
397
- updatedAt: number;
398
- lastErrorCode?: string;
399
- lastErrorMessage?: string;
400
- }
401
-
402
364
  /**
403
365
  * Durable control-plane record for a restore into a generated data-plane
404
366
  * scratch namespace. The namespace is not an IService and carries no image,
@@ -423,6 +385,9 @@ export interface IIsolatedRestoreRecord {
423
385
  scratchNamespaceId: string;
424
386
  /** Server-generated archive namespace, distinct from sourceBackupId. */
425
387
  stagingArchiveId: string;
388
+ /** The cluster node every control call is dispatched to, as the cluster document names it. */
389
+ targetNodeId: string;
390
+ /** The node name the signed restore grant authorizes, read from that node's cluster document. */
426
391
  targetNodeName: string;
427
392
  purpose: string;
428
393
  requestedBy: IIsolatedRestoreActor;
@@ -3,6 +3,8 @@ import * as plugins from '../plugins.js';
3
3
  import { type IDockerRegistryInfo } from '../data/docker.js';
4
4
  import type { IClusterNode } from './clusternode.js';
5
5
  import { isPlausibleUnicastIpv4 } from '../private/ipv4.js';
6
+ import { validateProtocolOfferOn, type IProtocolOffer } from '../protocol.js';
7
+ import { hasExactKeys } from '../private/dataproperties.js';
6
8
 
7
9
  export interface ICluster {
8
10
  id: string;
@@ -43,7 +45,7 @@ export interface ICluster {
43
45
  * How cluster-side components reach this cluster's relay. Absent on a
44
46
  * cluster whose nodes still target Cloudly directly.
45
47
  */
46
- relay?: IClusterRelayV1;
48
+ relay?: IClusterRelay;
47
49
  };
48
50
  }
49
51
 
@@ -56,15 +58,14 @@ export interface ICluster {
56
58
  * `new URL(origin).origin === origin`. A default `:443` is therefore written
57
59
  * implicitly, because `URL` drops it from the origin it computes.
58
60
  */
59
- export interface IClusterRelayV1 {
60
- schemaVersion: 1;
61
+ export interface IClusterRelay {
61
62
  origin: string;
62
63
  }
63
64
 
64
65
  /**
65
66
  * What a cluster relay tells Cloudly about itself once its socket is authenticated.
66
67
  *
67
- * The relay sends this after `registerCloudlyClientSessionV1`, so the cluster it registers for
68
+ * The relay sends this after `registerCloudlyClientSession`, so the cluster it registers for
68
69
  * is the one its verified machine identity names and is never a field here. `relay.nodeId` is
69
70
  * the cluster node the relay process runs on, `listenAddress` and `listenPort` are the exact
70
71
  * endpoint it bound for the Pallet nodes of its cluster, and `registrationSequence` counts up
@@ -72,8 +73,7 @@ export interface IClusterRelayV1 {
72
73
  * peer, where it must strictly increase, a refused registration does not consume it, and a
73
74
  * repeat of an accepted sequence is refused; across peers Cloudly's own acceptance time orders.
74
75
  */
75
- export interface IClusterRelayRegistrationV1 {
76
- schemaVersion: 1;
76
+ export interface IRegisterClusterRelayRequest {
77
77
  relay: {
78
78
  /** The relay build that is registering; a bounded label for operators and logs. */
79
79
  version: string;
@@ -91,6 +91,12 @@ export interface IClusterRelayRegistrationV1 {
91
91
  };
92
92
  /** Monotonic within one relay process; it restarts at 0 when the process restarts. */
93
93
  registrationSequence: number;
94
+ /**
95
+ * Read and negotiated before the registration is validated; see
96
+ * `validateRegisterClusterRelayRequest`. `relay.version` is the relay build and stays a
97
+ * different fact from `protocol.interfacesVersion`, which is the contract it speaks.
98
+ */
99
+ protocol: IProtocolOffer;
94
100
  }
95
101
 
96
102
  /**
@@ -101,11 +107,34 @@ export interface IClusterRelayRegistrationV1 {
101
107
  * registration always carries one: a cluster whose relay nobody enabled has no origin to
102
108
  * register into, and that registration is refused by name instead of answered.
103
109
  */
104
- export interface IClusterRelayRegistrationResponseV1 {
110
+ export interface IRegisterClusterRelayResponse {
105
111
  accepted: true;
106
112
  origin: string;
113
+ /** Cloudly's own offer, so an accepted relay judges the session it just registered into. */
114
+ protocol: IProtocolOffer;
107
115
  }
108
116
 
117
+ /**
118
+ * The registration refusals that end a relay's custody of its cluster.
119
+ *
120
+ * Each of them is Cloudly reading its own records and stating that this relay is not the one it
121
+ * dispatches to, so the answer outlives the socket it arrived on and a relay that hears one drops
122
+ * what it holds for its nodes instead of waiting. Cloudly's other answers — a refusal that only
123
+ * says it could not verify the relay, a malformed or conflicting registration, an internal error —
124
+ * state nothing about dispatch and are not listed here.
125
+ */
126
+ export const clusterRelayUndispatchedRefusals = Object.freeze([
127
+ /** The cluster has no relay enabled, so there is no registration to accept. */
128
+ 'relay-cluster-not-enabled',
129
+ /** The node the relay names belongs to another cluster than the one its identity names. */
130
+ 'relay-node-foreign',
131
+ /** A registration of this relay identity at the same or a higher sequence is already recorded. */
132
+ 'relay-sequence-not-advancing',
133
+ ] as const);
134
+
135
+ /** One of the reasons Cloudly states that it does not dispatch to a relay. */
136
+ export type TClusterRelayUndispatchedRefusal = typeof clusterRelayUndispatchedRefusals[number];
137
+
109
138
  const maximumClusterRelayOriginLength = 2048;
110
139
  const maximumClusterRelayVersionLength = 64;
111
140
 
@@ -117,16 +146,6 @@ const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
117
146
  || Object.getPrototypeOf(valueArg) === null)
118
147
  );
119
148
 
120
- const hasExactKeys = (
121
- valueArg: Record<string, unknown>,
122
- keysArg: readonly string[],
123
- ): boolean => {
124
- const actualKeys = Object.keys(valueArg).sort();
125
- const expectedKeys = [...keysArg].sort();
126
- return actualKeys.length === expectedKeys.length
127
- && actualKeys.every((keyArg, indexArg) => keyArg === expectedKeys[indexArg]);
128
- };
129
-
130
149
  const validateClusterRelayOrigin = (valueArg: unknown, pathArg: string): string[] => {
131
150
  if (typeof valueArg !== 'string'
132
151
  || valueArg.length === 0
@@ -162,19 +181,14 @@ const validateClusterRelayOrigin = (valueArg: unknown, pathArg: string): string[
162
181
  * identity store: it is the transport authority the node's runtime session
163
182
  * registers over.
164
183
  */
165
- export const validateClusterRelayV1 = (
184
+ export const validateClusterRelay = (
166
185
  valueArg: unknown,
167
186
  pathArg = 'cluster relay',
168
187
  ): string[] => {
169
- if (!isRecord(valueArg) || !hasExactKeys(valueArg, ['schemaVersion', 'origin'])) {
188
+ if (!isRecord(valueArg) || !hasExactKeys(valueArg, ['origin'])) {
170
189
  return [`${pathArg} must use its exact schema`];
171
190
  }
172
- const errors: string[] = [];
173
- if (valueArg.schemaVersion !== 1) {
174
- errors.push(`${pathArg} schemaVersion must be 1`);
175
- }
176
- errors.push(...validateClusterRelayOrigin(valueArg.origin, `${pathArg} origin`));
177
- return errors;
191
+ return validateClusterRelayOrigin(valueArg.origin, `${pathArg} origin`);
178
192
  };
179
193
 
180
194
  const validateClusterRelayVersion = (valueArg: unknown, pathArg: string): string[] => (
@@ -242,19 +256,22 @@ const validateClusterRelayEndpoint = (valueArg: unknown, pathArg: string): strin
242
256
  * stored one is Cloudly's decision too. `registrationSequence` restarts with the relay process,
243
257
  * so it orders registrations only within one live peer. A registration that passes here has
244
258
  * claimed nothing.
259
+ *
260
+ * Cloudly judges the body in the one server order `protocol.readProtocolOffer` states, against its
261
+ * `clusterRelay` offer, and reaches this check only afterwards — so a refused relay never consumes
262
+ * a sequence. The `validateProtocolOfferOn` call below is therefore the self-consistency check of
263
+ * a body that was already negotiated, never the negotiation step; it reads the offer the same way,
264
+ * as data, so naming reasons never runs an accessor a peer put on the body.
245
265
  */
246
- export const validateClusterRelayRegistrationV1 = (
266
+ export const validateRegisterClusterRelayRequest = (
247
267
  valueArg: unknown,
248
268
  pathArg = 'cluster relay registration',
249
269
  ): string[] => {
250
270
  if (!isRecord(valueArg)
251
- || !hasExactKeys(valueArg, ['schemaVersion', 'relay', 'registrationSequence'])) {
271
+ || !hasExactKeys(valueArg, ['relay', 'registrationSequence', 'protocol'])) {
252
272
  return [`${pathArg} must use its exact schema`];
253
273
  }
254
- const errors: string[] = [];
255
- if (valueArg.schemaVersion !== 1) {
256
- errors.push(`${pathArg} schemaVersion must be 1`);
257
- }
274
+ const errors: string[] = validateProtocolOfferOn(valueArg, `${pathArg} protocol`);
258
275
  errors.push(...validateClusterRelayEndpoint(valueArg.relay, `${pathArg} relay`));
259
276
  errors.push(...validateClusterRelayNonNegativeInteger(valueArg.registrationSequence,
260
277
  `${pathArg} registrationSequence`));
@@ -264,7 +281,7 @@ export const validateClusterRelayRegistrationV1 = (
264
281
  /**
265
282
  * One cluster's relay as Cloudly serves it to an administrator.
266
283
  *
267
- * This is the read shape, not the registration a relay sends: `IClusterRelayRegistrationV1` is
284
+ * This is the read shape, not the registration a relay sends: `IRegisterClusterRelayRequest` is
268
285
  * what the relay puts on the wire, and this is what Cloudly answers with after it accepted one.
269
286
  * `clusterId` names the cluster the relay serves, and `acceptedAt` is Cloudly's own acceptance
270
287
  * time, which is what orders two registrations of one cluster.
@@ -321,8 +338,8 @@ export const validateClusterRelayRegistration = (
321
338
  * identically; an origin that names an address literal instead of a domain
322
339
  * therefore has no issuable certificate.
323
340
  */
324
- export const clusterRelayCertificateDomainName = (relayArg: IClusterRelayV1): string => {
325
- const errors = validateClusterRelayV1(relayArg);
341
+ export const clusterRelayCertificateDomainName = (relayArg: IClusterRelay): string => {
342
+ const errors = validateClusterRelay(relayArg);
326
343
  if (errors.length > 0) {
327
344
  throw new Error(errors[0]);
328
345
  }
@@ -1,5 +1,7 @@
1
1
  import { isPlausibleUnicastIpv4 } from '../private/ipv4.js';
2
2
  import { isUtcCalendarDay } from '../private/utccalendarday.js';
3
+ import { validateProtocolOfferOn } from '../protocol.js';
4
+ import { hasExactKeys } from '../private/dataproperties.js';
3
5
 
4
6
  /**
5
7
  * The cluster ingress contracts.
@@ -119,16 +121,6 @@ const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
119
121
  || Object.getPrototypeOf(valueArg) === null)
120
122
  );
121
123
 
122
- const hasExactKeys = (
123
- valueArg: Record<string, unknown>,
124
- keysArg: readonly string[],
125
- optionalKeysArg: readonly string[] = [],
126
- ): boolean => {
127
- const actualKeys = Object.keys(valueArg);
128
- return keysArg.every((keyArg) => actualKeys.includes(keyArg))
129
- && actualKeys.every((keyArg) => keysArg.includes(keyArg) || optionalKeysArg.includes(keyArg));
130
- };
131
-
132
124
  const isCounter = (valueArg: unknown): valueArg is number => (
133
125
  Number.isSafeInteger(valueArg) && (valueArg as number) >= 0
134
126
  );
@@ -390,16 +382,24 @@ export const validateClusterTrafficStatisticsPage = (
390
382
  * which cluster is speaking comes from the identity tag on the relay's session.
391
383
  * A registration that passes here has proven nothing — and no reason it returns
392
384
  * ever quotes the bearer, because a reason travels into logs.
385
+ *
386
+ * Both hops judge the body in the one server order `protocol.readProtocolOffer`
387
+ * states, each against its own `clusterIngress` offer, so the relay refuses a peer
388
+ * it cannot serve without asking Cloudly and reaches this check only afterwards.
389
+ * The `validateProtocolOfferOn` call below is therefore the self-consistency
390
+ * check of a body that was already negotiated, never the negotiation step; it
391
+ * reads the offer the same way, as data, so naming reasons never runs an
392
+ * accessor a peer put on the body.
393
393
  */
394
394
  export const validateClusterIngressRegistration = (
395
395
  valueArg: unknown,
396
396
  pathArg = 'cluster ingress registration',
397
397
  ): string[] => {
398
398
  if (!isRecord(valueArg)
399
- || !hasExactKeys(valueArg, ['bearer', 'version', 'nodeId'], ['capabilities'])) {
399
+ || !hasExactKeys(valueArg, ['bearer', 'version', 'nodeId', 'protocol'], ['capabilities'])) {
400
400
  return [`${pathArg} must use its exact schema`];
401
401
  }
402
- const errors: string[] = [];
402
+ const errors: string[] = validateProtocolOfferOn(valueArg, `${pathArg} protocol`);
403
403
  if (typeof valueArg.bearer !== 'string'
404
404
  || valueArg.bearer.length < clusterIngressLimits.minimumIngressBearerLength
405
405
  || valueArg.bearer.length > clusterIngressLimits.maximumIngressBearerLength) {