@serve.zone/interfaces 31.4.0 → 31.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-17 - 31.5.0
4
+
5
+ ### Features
6
+
7
+ - Add the administrator read for cluster relays. `getClusterRelayRegistrations { identity,
8
+ clusterIds? }` answers with `IClusterRelayRegistration` per cluster — `clusterId`, `nodeId`,
9
+ `listenAddress`, `listenPort`, `version`, `registrationSequence`, Cloudly's own `acceptedAt`,
10
+ and `live`, whether a verified socket carries that relay at this instant. `live` is a liveness
11
+ fact and grants nothing: a current registration reads `live: false` while its socket is gone,
12
+ and a cluster whose relay never registered is absent from the answer rather than present with
13
+ an empty entry. `validateClusterRelayRegistration(value, pathArg?)` validates the read shape in
14
+ the package's value-free convention and reuses the rules the registration contract already
15
+ applies — the identifier rule, the unicast IPv4 rule with its loopback and link-local refusals,
16
+ the port range and the version label — so the two shapes can never drift apart. The
17
+ registration contract's own documentation is corrected at the same time: `registrationSequence`
18
+ restarts with the relay process, so it orders registrations only within one live peer, while
19
+ across peers a registration on a live verified socket supersedes the stored one. Nothing was
20
+ removed or renamed, and the write-side `IClusterRelayRegistrationV1` request keeps its
21
+ published name.
22
+ - Narrow `IClusterRelayRegistrationResponseV1.origin` from `string | null` to `string`. The only
23
+ implementer refuses a registration for a cluster whose relay nobody enabled — there is no
24
+ origin to register into — so it never answers with `null`, and a reader that had to handle one
25
+ was handling a case that cannot occur. Stricter for readers, and no consumer breaks: a value
26
+ that was permitted but never produced is simply no longer permitted.
27
+
3
28
  ## 2026-09-16 - 31.4.0
4
29
 
5
30
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '31.4.0',
6
+ version: '31.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -55,8 +55,9 @@ export interface IClusterRelayV1 {
55
55
  * is the one its verified machine identity names and is never a field here. `relay.nodeId` is
56
56
  * the cluster node the relay process runs on, `listenAddress` and `listenPort` are the exact
57
57
  * endpoint it bound for the Pallet nodes of its cluster, and `registrationSequence` counts up
58
- * once per registration within one relay process, so Cloudly can tell a newer registration from
59
- * a replayed older one while both peers are live.
58
+ * once per attempt within one relay process. It orders registrations only within one live
59
+ * peer, where it must strictly increase, a refused registration does not consume it, and a
60
+ * repeat of an accepted sequence is refused; across peers Cloudly's own acceptance time orders.
60
61
  */
61
62
  export interface IClusterRelayRegistrationV1 {
62
63
  schemaVersion: 1;
@@ -82,13 +83,13 @@ export interface IClusterRelayRegistrationV1 {
82
83
  * What Cloudly tells the relay back.
83
84
  *
84
85
  * `origin` is the origin Cloudly has published for this cluster, so a relay can check that the
85
- * certificate it holds and the name its nodes are told to reach are the same one. It is `null`
86
- * while the cluster carries no relay block, which a relay must treat as "not enabled yet"
87
- * rather than as an origin it may invent.
86
+ * certificate it holds and the name its nodes are told to reach are the same one. An accepted
87
+ * registration always carries one: a cluster whose relay nobody enabled has no origin to
88
+ * register into, and that registration is refused by name instead of answered.
88
89
  */
89
90
  export interface IClusterRelayRegistrationResponseV1 {
90
91
  accepted: true;
91
- origin: string | null;
92
+ origin: string;
92
93
  }
93
94
  /**
94
95
  * Validates one cluster relay block and names every reason it is refused.
@@ -103,11 +104,45 @@ export declare const validateClusterRelayV1: (valueArg: unknown, pathArg?: strin
103
104
  *
104
105
  * Shape only, exactly like every other validator here: which cluster the relay speaks for comes
105
106
  * from the verified identity on its socket, whether `relay.nodeId` is a node of that cluster is
106
- * Cloudly's to decide against its own records, and whether this registration supersedes a live
107
- * one is Cloudly's comparison of `registrationSequence` between peers. A registration that
108
- * passes here has claimed nothing.
107
+ * Cloudly's to decide against its own records, and whether this registration supersedes the
108
+ * stored one is Cloudly's decision too. `registrationSequence` restarts with the relay process,
109
+ * so it orders registrations only within one live peer. A registration that passes here has
110
+ * claimed nothing.
109
111
  */
110
112
  export declare const validateClusterRelayRegistrationV1: (valueArg: unknown, pathArg?: string) => string[];
113
+ /**
114
+ * One cluster's relay as Cloudly serves it to an administrator.
115
+ *
116
+ * This is the read shape, not the registration a relay sends: `IClusterRelayRegistrationV1` is
117
+ * what the relay puts on the wire, and this is what Cloudly answers with after it accepted one.
118
+ * `clusterId` names the cluster the relay serves, and `acceptedAt` is Cloudly's own acceptance
119
+ * time, which is what orders two registrations of one cluster.
120
+ */
121
+ export interface IClusterRelayRegistration {
122
+ clusterId: string;
123
+ nodeId: string;
124
+ listenAddress: string;
125
+ listenPort: number;
126
+ version: string;
127
+ registrationSequence: number;
128
+ acceptedAt: number;
129
+ /**
130
+ * Whether a verified socket carries this relay at this instant.
131
+ *
132
+ * A liveness fact and nothing else: it grants no authority, it is not part of what was
133
+ * registered, and it is false for a relay whose registration is perfectly current but whose
134
+ * socket is gone, so a reader must not treat it as a statement about the registration.
135
+ */
136
+ live: boolean;
137
+ }
138
+ /**
139
+ * Validates one served relay registration and names every reason it is refused.
140
+ *
141
+ * Shape only, like every validator here. Whether the cluster exists, whether the node still
142
+ * belongs to it and whether the relay is really connected are Cloudly's own records; a value
143
+ * that passes here has claimed nothing.
144
+ */
145
+ export declare const validateClusterRelayRegistration: (valueArg: unknown, pathArg?: string) => string[];
111
146
  /**
112
147
  * The name the relay's certificate is issued for, and the A record Cloudly
113
148
  * owns for it. Derived from the origin so every consumer derives it
@@ -64,8 +64,8 @@ const validateClusterRelayVersion = (valueArg, pathArg) => (typeof valueArg ===
64
64
  ? []
65
65
  : [`${pathArg} must be 1 to ${maximumClusterRelayVersionLength} characters of letters, `
66
66
  + 'digits, dot, plus, underscore or hyphen, starting with a letter or digit']);
67
- /** The same node id rule a node's own runtime session registers under. */
68
- const validateClusterRelayNodeId = (valueArg, pathArg) => (typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg)
67
+ /** The same identifier rule a node's own runtime session registers under. */
68
+ const validateClusterRelayIdentifier = (valueArg, pathArg) => (typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg)
69
69
  ? []
70
70
  : [`${pathArg} must be 1 to 128 characters of letters, digits, underscore or hyphen`]);
71
71
  const validateClusterRelayListenAddress = (valueArg, pathArg) => {
@@ -85,6 +85,9 @@ const validateClusterRelayListenAddress = (valueArg, pathArg) => {
85
85
  const validateClusterRelayListenPort = (valueArg, pathArg) => (typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 1 && valueArg <= 65535
86
86
  ? []
87
87
  : [`${pathArg} must be an integer from 1 to 65535`]);
88
+ const validateClusterRelayNonNegativeInteger = (valueArg, pathArg) => (typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 0
89
+ ? []
90
+ : [`${pathArg} must be a non-negative safe integer`]);
88
91
  const validateClusterRelayEndpoint = (valueArg, pathArg) => {
89
92
  if (!isRecord(valueArg)
90
93
  || !hasExactKeys(valueArg, ['version', 'nodeId', 'listenAddress', 'listenPort'])) {
@@ -92,7 +95,7 @@ const validateClusterRelayEndpoint = (valueArg, pathArg) => {
92
95
  }
93
96
  return [
94
97
  ...validateClusterRelayVersion(valueArg.version, `${pathArg} version`),
95
- ...validateClusterRelayNodeId(valueArg.nodeId, `${pathArg} nodeId`),
98
+ ...validateClusterRelayIdentifier(valueArg.nodeId, `${pathArg} nodeId`),
96
99
  ...validateClusterRelayListenAddress(valueArg.listenAddress, `${pathArg} listenAddress`),
97
100
  ...validateClusterRelayListenPort(valueArg.listenPort, `${pathArg} listenPort`),
98
101
  ];
@@ -102,9 +105,10 @@ const validateClusterRelayEndpoint = (valueArg, pathArg) => {
102
105
  *
103
106
  * Shape only, exactly like every other validator here: which cluster the relay speaks for comes
104
107
  * from the verified identity on its socket, whether `relay.nodeId` is a node of that cluster is
105
- * Cloudly's to decide against its own records, and whether this registration supersedes a live
106
- * one is Cloudly's comparison of `registrationSequence` between peers. A registration that
107
- * passes here has claimed nothing.
108
+ * Cloudly's to decide against its own records, and whether this registration supersedes the
109
+ * stored one is Cloudly's decision too. `registrationSequence` restarts with the relay process,
110
+ * so it orders registrations only within one live peer. A registration that passes here has
111
+ * claimed nothing.
108
112
  */
109
113
  export const validateClusterRelayRegistrationV1 = (valueArg, pathArg = 'cluster relay registration') => {
110
114
  if (!isRecord(valueArg)
@@ -116,13 +120,32 @@ export const validateClusterRelayRegistrationV1 = (valueArg, pathArg = 'cluster
116
120
  errors.push(`${pathArg} schemaVersion must be 1`);
117
121
  }
118
122
  errors.push(...validateClusterRelayEndpoint(valueArg.relay, `${pathArg} relay`));
119
- if (typeof valueArg.registrationSequence !== 'number'
120
- || !Number.isSafeInteger(valueArg.registrationSequence)
121
- || valueArg.registrationSequence < 0) {
122
- errors.push(`${pathArg} registrationSequence must be a non-negative safe integer`);
123
- }
123
+ errors.push(...validateClusterRelayNonNegativeInteger(valueArg.registrationSequence, `${pathArg} registrationSequence`));
124
124
  return errors;
125
125
  };
126
+ /**
127
+ * Validates one served relay registration and names every reason it is refused.
128
+ *
129
+ * Shape only, like every validator here. Whether the cluster exists, whether the node still
130
+ * belongs to it and whether the relay is really connected are Cloudly's own records; a value
131
+ * that passes here has claimed nothing.
132
+ */
133
+ export const validateClusterRelayRegistration = (valueArg, pathArg = 'cluster relay registration') => {
134
+ if (!isRecord(valueArg) || !hasExactKeys(valueArg, ['clusterId', 'nodeId', 'listenAddress',
135
+ 'listenPort', 'version', 'registrationSequence', 'acceptedAt', 'live'])) {
136
+ return [`${pathArg} must use its exact schema`];
137
+ }
138
+ return [
139
+ ...validateClusterRelayIdentifier(valueArg.clusterId, `${pathArg} clusterId`),
140
+ ...validateClusterRelayIdentifier(valueArg.nodeId, `${pathArg} nodeId`),
141
+ ...validateClusterRelayListenAddress(valueArg.listenAddress, `${pathArg} listenAddress`),
142
+ ...validateClusterRelayListenPort(valueArg.listenPort, `${pathArg} listenPort`),
143
+ ...validateClusterRelayVersion(valueArg.version, `${pathArg} version`),
144
+ ...validateClusterRelayNonNegativeInteger(valueArg.registrationSequence, `${pathArg} registrationSequence`),
145
+ ...validateClusterRelayNonNegativeInteger(valueArg.acceptedAt, `${pathArg} acceptedAt`),
146
+ ...(typeof valueArg.live === 'boolean' ? [] : [`${pathArg} live must be a boolean`]),
147
+ ];
148
+ };
126
149
  /**
127
150
  * The name the relay's certificate is issued for, and the A record Cloudly
128
151
  * owns for it. Derived from the origin so every consumer derives it
@@ -136,4 +159,4 @@ export const clusterRelayCertificateDomainName = (relayArg) => {
136
159
  }
137
160
  return new URL(relayArg.origin).hostname;
138
161
  };
139
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3Rlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEVBQTRCLE1BQU0sbUJBQW1CLENBQUM7QUFFN0QsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUF1RzVELE1BQU0sK0JBQStCLEdBQUcsSUFBSSxDQUFDO0FBQzdDLE1BQU0sZ0NBQWdDLEdBQUcsRUFBRSxDQUFDO0FBRTVDLE1BQU0sUUFBUSxHQUFHLENBQUMsUUFBaUIsRUFBdUMsRUFBRSxDQUFDLENBQzNFLE9BQU8sQ0FBQyxRQUFRLENBQUM7T0FDZCxPQUFPLFFBQVEsS0FBSyxRQUFRO09BQzVCLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUM7T0FDeEIsQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxLQUFLLE1BQU0sQ0FBQyxTQUFTO1dBQ25ELE1BQU0sQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLEtBQUssSUFBSSxDQUFDLENBQy9DLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxDQUNuQixRQUFpQyxFQUNqQyxPQUEwQixFQUNqQixFQUFFO0lBQ1gsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNoRCxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDekMsT0FBTyxVQUFVLENBQUMsTUFBTSxLQUFLLFlBQVksQ0FBQyxNQUFNO1dBQzNDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQyxNQUFNLEtBQUssWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDakYsQ0FBQyxD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
162
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3Rlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEVBQTRCLE1BQU0sbUJBQW1CLENBQUM7QUFFN0QsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUF3RzVELE1BQU0sK0JBQStCLEdBQUcsSUFBSSxDQUFDO0FBQzdDLE1BQU0sZ0NBQWdDLEdBQUcsRUFBRSxDQUFDO0FBRTVDLE1BQU0sUUFBUSxHQUFHLENBQUMsUUFBaUIsRUFBdUMsRUFBRSxDQUFDLENBQzNFLE9BQU8sQ0FBQyxRQUFRLENBQUM7T0FDZCxPQUFPLFFBQVEsS0FBSyxRQUFRO09BQzVCLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUM7T0FDeEIsQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxLQUFLLE1BQU0sQ0FBQyxTQUFTO1dBQ25ELE1BQU0sQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLEtBQUssSUFBSSxDQUFDLENBQy9DLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxDQUNuQixRQUFpQyxFQUNqQyxPQUEwQixFQUNqQixFQUFFO0lBQ1gsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNoRCxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDekMsT0FBTyxVQUFVLENBQUMsTUFBTSxLQUFLLFlBQVksQ0FBQyxNQUFNO1dBQzNDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQyxNQUFNLEtBQUssWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDakYsQ0FBQyxD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
@@ -83,6 +83,23 @@ export interface IReq_Any_Cloudly_DisableClusterRelayV1 extends plugins.typedreq
83
83
  relay: null;
84
84
  };
85
85
  }
86
+ /**
87
+ * The relay registrations an administrator asks Cloudly about.
88
+ *
89
+ * Read-only: it answers with what Cloudly accepted, plus whether a verified socket carries each
90
+ * relay at this instant. `clusterIds` narrows the answer to the named clusters; omitting it asks
91
+ * for every cluster whose relay ever registered.
92
+ */
93
+ export interface IReq_Any_Cloudly_GetClusterRelayRegistrations extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetClusterRelayRegistrations> {
94
+ method: 'getClusterRelayRegistrations';
95
+ request: {
96
+ identity: userInterfaces.IIdentity;
97
+ clusterIds?: string[];
98
+ };
99
+ response: {
100
+ registrations: clusterInterfaces.IClusterRelayRegistration[];
101
+ };
102
+ }
86
103
  /**
87
104
  * The cluster relay tells Cloudly where it listens, over its own authenticated socket.
88
105
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "31.4.0",
3
+ "version": "31.5.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
package/readme.md CHANGED
@@ -1132,12 +1132,27 @@ refused by its own name, because a relay that bound one can never serve the node
1132
1132
 
1133
1133
  Authority stays with Cloudly, and none of it is a request field. The cluster is the one the
1134
1134
  verified machine identity on that socket names; whether `relay.nodeId` is a node of that cluster
1135
- is decided against Cloudly's own records; and one cluster has one relay, so when two peers are
1136
- live the one holding the highest accepted `registrationSequence` is the relay and the other is
1137
- never selected. The response carries the origin Cloudly published, or `null` while the cluster
1138
- carries no relay block, which a relay must read as "not enabled yet" rather than as licence to
1139
- invent an origin: it is how the relay checks that the certificate it holds and the name its nodes
1140
- are told to reach are the same one.
1135
+ is decided against Cloudly's own records; and one cluster has one relay. A registration for a
1136
+ cluster whose relay nobody enabled is refused by name, because there is no origin to register
1137
+ into, so an accepted registration always answers with the origin Cloudly published: that is how
1138
+ a relay checks that the certificate it holds and the name its nodes are told to reach are the
1139
+ same one.
1140
+
1141
+ `registrationSequence` restarts with the relay process, so it orders registrations only within
1142
+ one live peer: there it must strictly increase, a refused registration does not consume it (the
1143
+ same body may be re-sent once the cause is gone), and a retry that repeats a sequence Cloudly
1144
+ already accepted is refused. Across peers it says nothing, because a restarted relay counts from
1145
+ zero again; there Cloudly's own acceptance time orders the registrations, and a registration
1146
+ arriving on a live verified socket supersedes the stored one.
1147
+
1148
+ An administrator reads the result with `getClusterRelayRegistrations { identity, clusterIds? }`,
1149
+ answered with `IClusterRelayRegistration` per cluster: the cluster, the node, the listen address
1150
+ and port, the relay version, the sequence that relay counted, Cloudly's `acceptedAt` — and
1151
+ `live`, whether a verified socket carries that relay at this instant. `live` is a liveness fact
1152
+ and grants nothing: a registration that is perfectly current reads `live: false` while its socket
1153
+ is gone, and a cluster whose relay never registered is absent from the answer rather than
1154
+ present with an empty one. `validateClusterRelayRegistration(value, path?)` returns one named
1155
+ reason per violation, as every validator here does.
1141
1156
 
1142
1157
  ```typescript
1143
1158
  const registration: data.IClusterRelayRegistrationV1 = {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '31.4.0',
6
+ version: '31.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -68,8 +68,9 @@ export interface IClusterRelayV1 {
68
68
  * is the one its verified machine identity names and is never a field here. `relay.nodeId` is
69
69
  * the cluster node the relay process runs on, `listenAddress` and `listenPort` are the exact
70
70
  * endpoint it bound for the Pallet nodes of its cluster, and `registrationSequence` counts up
71
- * once per registration within one relay process, so Cloudly can tell a newer registration from
72
- * a replayed older one while both peers are live.
71
+ * once per attempt within one relay process. It orders registrations only within one live
72
+ * peer, where it must strictly increase, a refused registration does not consume it, and a
73
+ * repeat of an accepted sequence is refused; across peers Cloudly's own acceptance time orders.
73
74
  */
74
75
  export interface IClusterRelayRegistrationV1 {
75
76
  schemaVersion: 1;
@@ -96,13 +97,13 @@ export interface IClusterRelayRegistrationV1 {
96
97
  * What Cloudly tells the relay back.
97
98
  *
98
99
  * `origin` is the origin Cloudly has published for this cluster, so a relay can check that the
99
- * certificate it holds and the name its nodes are told to reach are the same one. It is `null`
100
- * while the cluster carries no relay block, which a relay must treat as "not enabled yet"
101
- * rather than as an origin it may invent.
100
+ * certificate it holds and the name its nodes are told to reach are the same one. An accepted
101
+ * registration always carries one: a cluster whose relay nobody enabled has no origin to
102
+ * register into, and that registration is refused by name instead of answered.
102
103
  */
103
104
  export interface IClusterRelayRegistrationResponseV1 {
104
105
  accepted: true;
105
- origin: string | null;
106
+ origin: string;
106
107
  }
107
108
 
108
109
  const maximumClusterRelayOriginLength = 2048;
@@ -185,8 +186,8 @@ const validateClusterRelayVersion = (valueArg: unknown, pathArg: string): string
185
186
  + 'digits, dot, plus, underscore or hyphen, starting with a letter or digit']
186
187
  );
187
188
 
188
- /** The same node id rule a node's own runtime session registers under. */
189
- const validateClusterRelayNodeId = (valueArg: unknown, pathArg: string): string[] => (
189
+ /** The same identifier rule a node's own runtime session registers under. */
190
+ const validateClusterRelayIdentifier = (valueArg: unknown, pathArg: string): string[] => (
190
191
  typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg)
191
192
  ? []
192
193
  : [`${pathArg} must be 1 to 128 characters of letters, digits, underscore or hyphen`]
@@ -213,6 +214,12 @@ const validateClusterRelayListenPort = (valueArg: unknown, pathArg: string): str
213
214
  : [`${pathArg} must be an integer from 1 to 65535`]
214
215
  );
215
216
 
217
+ const validateClusterRelayNonNegativeInteger = (valueArg: unknown, pathArg: string): string[] => (
218
+ typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 0
219
+ ? []
220
+ : [`${pathArg} must be a non-negative safe integer`]
221
+ );
222
+
216
223
  const validateClusterRelayEndpoint = (valueArg: unknown, pathArg: string): string[] => {
217
224
  if (!isRecord(valueArg)
218
225
  || !hasExactKeys(valueArg, ['version', 'nodeId', 'listenAddress', 'listenPort'])) {
@@ -220,7 +227,7 @@ const validateClusterRelayEndpoint = (valueArg: unknown, pathArg: string): strin
220
227
  }
221
228
  return [
222
229
  ...validateClusterRelayVersion(valueArg.version, `${pathArg} version`),
223
- ...validateClusterRelayNodeId(valueArg.nodeId, `${pathArg} nodeId`),
230
+ ...validateClusterRelayIdentifier(valueArg.nodeId, `${pathArg} nodeId`),
224
231
  ...validateClusterRelayListenAddress(valueArg.listenAddress, `${pathArg} listenAddress`),
225
232
  ...validateClusterRelayListenPort(valueArg.listenPort, `${pathArg} listenPort`),
226
233
  ];
@@ -231,9 +238,10 @@ const validateClusterRelayEndpoint = (valueArg: unknown, pathArg: string): strin
231
238
  *
232
239
  * Shape only, exactly like every other validator here: which cluster the relay speaks for comes
233
240
  * from the verified identity on its socket, whether `relay.nodeId` is a node of that cluster is
234
- * Cloudly's to decide against its own records, and whether this registration supersedes a live
235
- * one is Cloudly's comparison of `registrationSequence` between peers. A registration that
236
- * passes here has claimed nothing.
241
+ * Cloudly's to decide against its own records, and whether this registration supersedes the
242
+ * stored one is Cloudly's decision too. `registrationSequence` restarts with the relay process,
243
+ * so it orders registrations only within one live peer. A registration that passes here has
244
+ * claimed nothing.
237
245
  */
238
246
  export const validateClusterRelayRegistrationV1 = (
239
247
  valueArg: unknown,
@@ -248,14 +256,65 @@ export const validateClusterRelayRegistrationV1 = (
248
256
  errors.push(`${pathArg} schemaVersion must be 1`);
249
257
  }
250
258
  errors.push(...validateClusterRelayEndpoint(valueArg.relay, `${pathArg} relay`));
251
- if (typeof valueArg.registrationSequence !== 'number'
252
- || !Number.isSafeInteger(valueArg.registrationSequence)
253
- || valueArg.registrationSequence < 0) {
254
- errors.push(`${pathArg} registrationSequence must be a non-negative safe integer`);
255
- }
259
+ errors.push(...validateClusterRelayNonNegativeInteger(valueArg.registrationSequence,
260
+ `${pathArg} registrationSequence`));
256
261
  return errors;
257
262
  };
258
263
 
264
+ /**
265
+ * One cluster's relay as Cloudly serves it to an administrator.
266
+ *
267
+ * This is the read shape, not the registration a relay sends: `IClusterRelayRegistrationV1` is
268
+ * what the relay puts on the wire, and this is what Cloudly answers with after it accepted one.
269
+ * `clusterId` names the cluster the relay serves, and `acceptedAt` is Cloudly's own acceptance
270
+ * time, which is what orders two registrations of one cluster.
271
+ */
272
+ export interface IClusterRelayRegistration {
273
+ clusterId: string;
274
+ nodeId: string;
275
+ listenAddress: string;
276
+ listenPort: number;
277
+ version: string;
278
+ registrationSequence: number;
279
+ acceptedAt: number;
280
+ /**
281
+ * Whether a verified socket carries this relay at this instant.
282
+ *
283
+ * A liveness fact and nothing else: it grants no authority, it is not part of what was
284
+ * registered, and it is false for a relay whose registration is perfectly current but whose
285
+ * socket is gone, so a reader must not treat it as a statement about the registration.
286
+ */
287
+ live: boolean;
288
+ }
289
+
290
+ /**
291
+ * Validates one served relay registration and names every reason it is refused.
292
+ *
293
+ * Shape only, like every validator here. Whether the cluster exists, whether the node still
294
+ * belongs to it and whether the relay is really connected are Cloudly's own records; a value
295
+ * that passes here has claimed nothing.
296
+ */
297
+ export const validateClusterRelayRegistration = (
298
+ valueArg: unknown,
299
+ pathArg = 'cluster relay registration',
300
+ ): string[] => {
301
+ if (!isRecord(valueArg) || !hasExactKeys(valueArg, ['clusterId', 'nodeId', 'listenAddress',
302
+ 'listenPort', 'version', 'registrationSequence', 'acceptedAt', 'live'])) {
303
+ return [`${pathArg} must use its exact schema`];
304
+ }
305
+ return [
306
+ ...validateClusterRelayIdentifier(valueArg.clusterId, `${pathArg} clusterId`),
307
+ ...validateClusterRelayIdentifier(valueArg.nodeId, `${pathArg} nodeId`),
308
+ ...validateClusterRelayListenAddress(valueArg.listenAddress, `${pathArg} listenAddress`),
309
+ ...validateClusterRelayListenPort(valueArg.listenPort, `${pathArg} listenPort`),
310
+ ...validateClusterRelayVersion(valueArg.version, `${pathArg} version`),
311
+ ...validateClusterRelayNonNegativeInteger(valueArg.registrationSequence,
312
+ `${pathArg} registrationSequence`),
313
+ ...validateClusterRelayNonNegativeInteger(valueArg.acceptedAt, `${pathArg} acceptedAt`),
314
+ ...(typeof valueArg.live === 'boolean' ? [] : [`${pathArg} live must be a boolean`]),
315
+ ];
316
+ };
317
+
259
318
  /**
260
319
  * The name the relay's certificate is issued for, and the A record Cloudly
261
320
  * owns for it. Derived from the origin so every consumer derives it
@@ -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
  *