@serve.zone/interfaces 31.3.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.
@@ -66,10 +66,14 @@ export const captureNetwork = (value) => networkSafe(() => {
66
66
  requireNetwork(encoder.encode(networkCanonical(result)).length <= 1_048_576);
67
67
  return result;
68
68
  });
69
- export const networkRecord = (value, keys) => {
69
+ export const networkRecord = (value, keys, optionalKeys = []) => {
70
70
  requireNetwork(value !== null && typeof value === 'object' && !Array.isArray(value));
71
71
  const actual = Object.keys(value);
72
- requireNetwork(actual.length === keys.length && keys.every(key => Object.hasOwn(value, key)));
72
+ // Absence stays absent: a stated optional key is read, an omitted one is not
73
+ // defaulted, so a receiver can tell a sender that stated nothing from one that
74
+ // stated a value.
75
+ requireNetwork(keys.every(key => Object.hasOwn(value, key))
76
+ && actual.every(key => keys.includes(key) || optionalKeys.includes(key)));
73
77
  return value;
74
78
  };
75
79
  export const networkList = (value, maximum, read) => {
@@ -131,4 +135,4 @@ export const networkOverlaps = (left, right) => {
131
135
  const [a, b] = networkPrefixBounds(left), [c, d] = networkPrefixBounds(right);
132
136
  return a <= d && c <= b;
133
137
  };
134
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvcHJpdmF0ZS9uZXR3b3JrY29udHJhY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLHNCQUFzQixFQUFFLDRCQUE0QixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFMUYsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLEdBQVUsRUFBRSxHQUFHLE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM5RyxNQUFNLFVBQVUsY0FBYyxDQUFDLEtBQWMsSUFBbUIsSUFBSSxDQUFDLEtBQUs7SUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDNUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFjLEVBQVUsRUFBRSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsQ0FBQztBQUN2RyxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxJQUFhLEVBQUUsS0FBYyxFQUFXLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsS0FBSyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUMzSCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsQ0FBSSxTQUFrQixFQUFLLEVBQUUsR0FBRyxJQUFJLENBQUM7SUFBQyxPQUFPLFNBQVMsRUFBRSxDQUFDO0FBQUMsQ0FBQztBQUFDLE1BQU0sQ0FBQztJQUFDLE9BQU8sV0FBVyxFQUFFLENBQUM7QUFBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRXpILDJGQUEyRjtBQUMzRixNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxLQUFjLEVBQVcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDMUUsTUFBTSxPQUFPLEdBQUcsSUFBSSxXQUFXLEVBQUUsRUFBRSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVB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
138
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvcHJpdmF0ZS9uZXR3b3JrY29udHJhY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLHNCQUFzQixFQUFFLDRCQUE0QixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFMUYsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLEdBQVUsRUFBRSxHQUFHLE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM5RyxNQUFNLFVBQVUsY0FBYyxDQUFDLEtBQWMsSUFBbUIsSUFBSSxDQUFDLEtBQUs7SUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDNUYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFjLEVBQVUsRUFBRSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsQ0FBQztBQUN2RyxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxJQUFhLEVBQUUsS0FBYyxFQUFXLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsS0FBSyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUMzSCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsQ0FBSSxTQUFrQixFQUFLLEVBQUUsR0FBRyxJQUFJLENBQUM7SUFBQyxPQUFPLFNBQVMsRUFBRSxDQUFDO0FBQUMsQ0FBQztBQUFDLE1BQU0sQ0FBQztJQUFDLE9BQU8sV0FBVyxFQUFFLENBQUM7QUFBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRXpILDJGQUEyRjtBQUMzRixNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxLQUFjLEVBQVcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDMUUsTUFBTSxPQUFPLEdBQUcsSUFBSSxXQUFXLEVBQUUsRUFBRSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVB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
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The UTC calendar day every consumer of this package agrees on.
3
+ *
4
+ * Two facts, deliberately separate: whether a value has the canonical shape, and whether that
5
+ * shape names a day that exists. `2026-13-45` passes the first and fails the second, and a
6
+ * caller that only checked the pattern would store a day no clock can ever produce.
7
+ */
8
+ export declare const utcCalendarDayPattern: RegExp;
9
+ /**
10
+ * Whether a canonical `YYYY-MM-DD` string names a real day.
11
+ *
12
+ * Round-trips through `Date.UTC`, which normalises an impossible day into a neighbouring one:
13
+ * a value that survives the round trip unchanged is the day it claims to be.
14
+ */
15
+ export declare const isRealUtcCalendarDay: (valueArg: string) => boolean;
16
+ /** Both facts at once, for a caller that reports one reason. */
17
+ export declare const isUtcCalendarDay: (valueArg: unknown) => valueArg is string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The UTC calendar day every consumer of this package agrees on.
3
+ *
4
+ * Two facts, deliberately separate: whether a value has the canonical shape, and whether that
5
+ * shape names a day that exists. `2026-13-45` passes the first and fails the second, and a
6
+ * caller that only checked the pattern would store a day no clock can ever produce.
7
+ */
8
+ export const utcCalendarDayPattern = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
9
+ /**
10
+ * Whether a canonical `YYYY-MM-DD` string names a real day.
11
+ *
12
+ * Round-trips through `Date.UTC`, which normalises an impossible day into a neighbouring one:
13
+ * a value that survives the round trip unchanged is the day it claims to be.
14
+ */
15
+ export const isRealUtcCalendarDay = (valueArg) => {
16
+ if (!utcCalendarDayPattern.test(valueArg)) {
17
+ return false;
18
+ }
19
+ const [year, month, day] = valueArg.split('-').map(Number);
20
+ const parsed = new Date(Date.UTC(year, month - 1, day));
21
+ return parsed.getUTCFullYear() === year
22
+ && parsed.getUTCMonth() + 1 === month
23
+ && parsed.getUTCDate() === day;
24
+ };
25
+ /** Both facts at once, for a caller that reports one reason. */
26
+ export const isUtcCalendarDay = (valueArg) => (typeof valueArg === 'string' && isRealUtcCalendarDay(valueArg));
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRjY2FsZW5kYXJkYXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9wcml2YXRlL3V0Y2NhbGVuZGFyZGF5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLDhCQUE4QixDQUFDO0FBRXBFOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxRQUFnQixFQUFXLEVBQUU7SUFDaEUsSUFBSSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQzFDLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUNELE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzNELE1BQU0sTUFBTSxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLEtBQUssR0FBRyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RCxPQUFPLE1BQU0sQ0FBQyxjQUFjLEVBQUUsS0FBSyxJQUFJO1dBQ2xDLE1BQU0sQ0FBQyxXQUFXLEVBQUUsR0FBRyxDQUFDLEtBQUssS0FBSztXQUNsQyxNQUFNLENBQUMsVUFBVSxFQUFFLEtBQUssR0FBRyxDQUFDO0FBQ25DLENBQUMsQ0FBQztBQUVGLGdFQUFnRTtBQUNoRSxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFFBQWlCLEVBQXNCLEVBQUUsQ0FBQyxDQUN6RSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksb0JBQW9CLENBQUMsUUFBUSxDQUFDLENBQy9ELENBQUMifQ==
@@ -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
  *
@@ -0,0 +1,120 @@
1
+ import * as clusterIngressInterfaces from '../data/clusteringress.js';
2
+ import * as plugins from '../plugins.js';
3
+ /**
4
+ * Cloudly hands a cluster's relay the table its ingress serves.
5
+ *
6
+ * Cloudly computes it: it is the only party that knows every service, every
7
+ * ready endpoint, the node address each published port answers on and the
8
+ * certificate each hostname needs. The relay caches it in memory and serves it
9
+ * to the ingress; it never authors one.
10
+ *
11
+ * The table carries private keys, so this request is secret-bearing: exclude it
12
+ * from hooks, logs and journals on both sides.
13
+ */
14
+ export interface IReq_Cloudly_Relay_PushClusterRouteTable extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Relay_PushClusterRouteTable> {
15
+ method: 'pushClusterRouteTable';
16
+ request: {
17
+ table: clusterIngressInterfaces.IClusterRouteTable;
18
+ };
19
+ response: {
20
+ /**
21
+ * The revision the relay now holds. Lower than the pushed revision means
22
+ * the relay already held a newer table and kept it.
23
+ */
24
+ appliedRevision: number;
25
+ };
26
+ }
27
+ /**
28
+ * The relay asks for the current table.
29
+ *
30
+ * Its own session decides which cluster it speaks for, so the request carries no
31
+ * cluster of its own. `null` means Cloudly has computed no table yet — a fact,
32
+ * not an empty table, which the relay must not serve as one.
33
+ */
34
+ export interface IReq_Relay_Cloudly_GetClusterRouteTable extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Relay_Cloudly_GetClusterRouteTable> {
35
+ method: 'getClusterRouteTable';
36
+ request: Record<string, never>;
37
+ response: {
38
+ table: clusterIngressInterfaces.IClusterRouteTable | null;
39
+ };
40
+ }
41
+ /**
42
+ * The cluster ingress announces itself, and Cloudly decides whether it is one.
43
+ *
44
+ * One body for both hops: the ingress sends it to its relay, and the relay
45
+ * forwards it to Cloudly untouched. The relay cannot decide this — anything that
46
+ * reaches its listener could ask — so the bearer Cloudly minted into the ingress
47
+ * workload's assignment environment is what authorizes the registration, and
48
+ * Cloudly derives which cluster is speaking from the identity tag on the relay's
49
+ * own session, never from a request field.
50
+ *
51
+ * A method, not a connection tag: what a relay knows about its ingress is what
52
+ * it registered through, which a server owns, rather than a label a client
53
+ * authored. Only an accepted registration binds the connection; every other
54
+ * ingress method is answered for that connection alone.
55
+ *
56
+ * `revision` is the table the relay holds, so an accepted ingress knows at once
57
+ * whether it has to fetch one. Cloudly answers `null`: the table is the relay's
58
+ * to state, not Cloudly's to echo.
59
+ */
60
+ export interface IReq_Ingress_Relay_Cloudly_RegisterClusterIngress extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Ingress_Relay_Cloudly_RegisterClusterIngress> {
61
+ method: 'registerClusterIngress';
62
+ request: {
63
+ /**
64
+ * Sensitive ephemeral input: exclude the complete request from hooks, logs,
65
+ * diagnostics and durable journals on every hop. Cloudly mints it into the
66
+ * ingress workload's assignment environment and verifies it here.
67
+ */
68
+ bearer: string;
69
+ /** The ingress build that registered, for operators reading a relay's log. */
70
+ version: string;
71
+ /** The node this ingress runs on, as its assignment names it. */
72
+ nodeId: string;
73
+ /** Optional capability names; an ingress that states none states nothing. */
74
+ capabilities?: string[];
75
+ };
76
+ response: {
77
+ accepted: true;
78
+ revision: number | null;
79
+ };
80
+ }
81
+ /**
82
+ * The ingress fetches the table the relay holds.
83
+ *
84
+ * `knownRevision` is what the ingress already applied: the relay answers `null`
85
+ * when it holds nothing newer, so a reconnect costs one round trip and not a
86
+ * certificate transfer.
87
+ */
88
+ export interface IReq_Ingress_Relay_GetClusterIngressRouteTable extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Ingress_Relay_GetClusterIngressRouteTable> {
89
+ method: 'getClusterIngressRouteTable';
90
+ request: {
91
+ knownRevision: number | null;
92
+ };
93
+ response: {
94
+ table: clusterIngressInterfaces.IClusterRouteTable | null;
95
+ };
96
+ }
97
+ /**
98
+ * One page of the ingress's day-bucketed counters.
99
+ *
100
+ * The same body travels Cloudly → relay and relay → ingress: the relay carries
101
+ * the request to the ingress that answers it and the page back, adding nothing.
102
+ * A reader re-reads yesterday and today on every pass, because a day only stops
103
+ * changing once it has closed.
104
+ */
105
+ export interface IReq_Any_Ingress_GetClusterTrafficStatistics extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Ingress_GetClusterTrafficStatistics> {
106
+ method: 'getClusterTrafficStatistics';
107
+ request: {
108
+ /** Inclusive first UTC calendar day, `YYYY-MM-DD`. */
109
+ fromDayUtc: string;
110
+ /** Inclusive last UTC calendar day, `YYYY-MM-DD`. */
111
+ toDayUtc: string;
112
+ /** Opaque ingress-owned cursor. */
113
+ cursor?: string;
114
+ limit: number;
115
+ };
116
+ response: {
117
+ statistics: clusterIngressInterfaces.IClusterTrafficStatistics[];
118
+ nextCursor?: string;
119
+ };
120
+ }
@@ -0,0 +1,3 @@
1
+ import * as clusterIngressInterfaces from '../data/clusteringress.js';
2
+ import * as plugins from '../plugins.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcmluZ3Jlc3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9yZXF1ZXN0cy9jbHVzdGVyaW5ncmVzcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssd0JBQXdCLE1BQU0sMkJBQTJCLENBQUM7QUFDdEUsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUMifQ==
@@ -5,6 +5,7 @@ import * as baseOsRequests from './baseos.js';
5
5
  import * as backupRequests from './backup.js';
6
6
  import * as certificateRequests from './certificate.js';
7
7
  import * as clusterRequests from './cluster.js';
8
+ import * as clusterIngressRequests from './clusteringress.js';
8
9
  import * as configRequests from './config.js';
9
10
  import * as coreMailRequests from './coremail.js';
10
11
  import * as coreMailAdminRequests from './coremail.admin.js';
@@ -34,7 +35,7 @@ import * as statusRequests from './status.js';
34
35
  import * as taskRequests from './task.js';
35
36
  import * as versionRequests from './version.js';
36
37
  import * as webPushRequests from './webpush.js';
37
- export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, coreMailAdminRequests as coremailAdmin, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, runtimeSessionRequests as runtimesession, secretRequests as secret, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
38
+ export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, clusterIngressRequests as clusteringress, configRequests as config, coreMailRequests as coremail, coreMailAdminRequests as coremailAdmin, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, runtimeSessionRequests as runtimesession, secretRequests as secret, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
38
39
  export * from './inform.js';
39
40
  export * from './hostedapp.js';
40
41
  export * from './mail.js';
@@ -6,6 +6,7 @@ import * as baseOsRequests from './baseos.js';
6
6
  import * as backupRequests from './backup.js';
7
7
  import * as certificateRequests from './certificate.js';
8
8
  import * as clusterRequests from './cluster.js';
9
+ import * as clusterIngressRequests from './clusteringress.js';
9
10
  import * as configRequests from './config.js';
10
11
  import * as coreMailRequests from './coremail.js';
11
12
  import * as coreMailAdminRequests from './coremail.admin.js';
@@ -35,9 +36,9 @@ import * as statusRequests from './status.js';
35
36
  import * as taskRequests from './task.js';
36
37
  import * as versionRequests from './version.js';
37
38
  import * as webPushRequests from './webpush.js';
38
- export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, coreMailAdminRequests as coremailAdmin, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, runtimeSessionRequests as runtimesession, secretRequests as secret, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
39
+ export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, clusterIngressRequests as clusteringress, configRequests as config, coreMailRequests as coremail, coreMailAdminRequests as coremailAdmin, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, runtimeSessionRequests as runtimesession, secretRequests as secret, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
39
40
  export * from './inform.js';
40
41
  export * from './hostedapp.js';
41
42
  export * from './mail.js';
42
43
  export * from './webpush.js';
43
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9yZXF1ZXN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEtBQUssYUFBYSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEtBQUssZ0JBQWdCLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sS0FBSyxpQkFBaUIsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssbUJBQW1CLE1BQU0sa0JBQWtCLENBQUM7QUFDeEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLGdCQUFnQixNQUFNLGVBQWUsQ0FBQztBQUNsRCxPQUFPLEtBQUsscUJBQXFCLE1BQU0scUJBQXFCLENBQUM7QUFDN0QsT0FBTyxLQUFLLGlCQUFpQixNQUFNLGdCQUFnQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxrQkFBa0IsTUFBTSxpQkFBaUIsQ0FBQztBQUN0RCxPQUFPLEtBQUssV0FBVyxNQUFNLFVBQVUsQ0FBQztBQUN4QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssd0JBQXdCLE1BQU0sdUJBQXVCLENBQUM7QUFDbEUsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLGlCQUFpQixNQUFNLGdCQUFnQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSxlQUFlLENBQUM7QUFDbEQsT0FBTyxLQUFLLGFBQWEsTUFBTSxZQUFZLENBQUM7QUFDNUMsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLFdBQVcsTUFBTSxVQUFVLENBQUM7QUFDeEMsT0FBTyxLQUFLLFlBQVksTUFBTSxXQUFXLENBQUM7QUFDMUMsT0FBTyxLQUFLLGlCQUFpQixNQUFNLGdCQUFnQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sS0FBSyxZQUFZLE1BQU0sV0FBVyxDQUFDO0FBQzFDLE9BQU8sS0FBSyxjQUFjLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSxlQUFlLENBQUM7QUFDbEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLHNCQUFzQixNQUFNLHFCQUFxQixDQUFDO0FBQzlELE9BQU8sS0FBSyxjQUFjLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSxlQUFlLENBQUM7QUFDbEQsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLFlBQVksTUFBTSxXQUFXLENBQUM7QUFDMUMsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFFaEQsT0FBTyxFQUNMLGFBQWEsSUFBSSxLQUFLLEVBQ3RCLGdCQUFnQixJQUFJLFFBQVEsRUFDNUIsaUJBQWlCLElBQUksU0FBUyxFQUM5QixjQUFjLElBQUksTUFBTSxFQUN4QixjQUFjLElBQUksTUFBTSxFQUN4QixtQkFBbUIsSUFBSSxXQUFXLEVBQ2xDLGVBQWUsSUFBSSxPQUFPLEVBQzFCLGNBQWMsSUFBSSxNQUFNLEVBQ3hCLGdCQUFnQixJQUFJLFFBQVEsRUFDNUIscUJBQXFCLElBQUksYUFBYSxFQUN0QyxpQkFBaUIsSUFBSSxTQUFTLEVBQzlCLGtCQUFrQixJQUFJLFVBQVUsRUFDaEMsV0FBVyxJQUFJLEdBQUcsRUFDbEIsY0FBYyxJQUFJLE1BQU0sRUFDeEIsd0JBQXdCLElBQUksZ0JBQWdCLEVBQzVDLGVBQWUsSUFBSSxPQUFPLEVBQzFCLGlCQUFpQixJQUFJLFNBQVMsRUFDOUIsZ0JBQWdCLElBQUksUUFBUSxFQUM1QixhQUFhLElBQUksS0FBSyxFQUN0QixjQUFjLElBQUksTUFBTSxFQUN4QixXQUFXLElBQUksR0FBRyxFQUNsQixZQUFZLElBQUksSUFBSSxFQUNwQixpQkFBaUIsSUFBSSxTQUFTLEVBQzlCLGVBQWUsSUFBSSxPQUFPLEVBQzFCLFlBQVksSUFBSSxJQUFJLEVBQ3BCLGNBQWMsSUFBSSxNQUFNLEVBQ3hCLGdCQUFnQixJQUFJLFFBQVEsRUFDNUIsZUFBZSxJQUFJLE9BQU8sRUFDMUIsc0JBQXNCLElBQUksY0FBYyxFQUN4QyxjQUFjLElBQUksTUFBTSxFQUN4QixlQUFlLElBQUksT0FBTyxFQUMxQixnQkFBZ0IsSUFBSSxRQUFRLEVBQzVCLGNBQWMsSUFBSSxNQUFNLEVBQ3hCLFlBQVksSUFBSSxJQUFJLEVBQ3BCLGVBQWUsSUFBSSxPQUFPLEVBQzFCLGVBQWUsSUFBSSxPQUFPLEdBQzNCLENBQUM7QUFFRixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsY0FBYyxDQUFDIn0=
44
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9yZXF1ZXN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEtBQUssYUFBYSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEtBQUssZ0JBQWdCLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sS0FBSyxpQkFBaUIsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssbUJBQW1CLE1BQU0sa0JBQWtCLENBQUM7QUFDeEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLHNCQUFzQixNQUFNLHFCQUFxQixDQUFDO0FBQzlELE9BQU8sS0FBSyxjQUFjLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSxlQUFlLENBQUM7QUFDbEQsT0FBTyxLQUFLLHFCQUFxQixNQUFNLHFCQUFxQixDQUFDO0FBQzdELE9BQU8sS0FBSyxpQkFBaUIsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEtBQUssa0JBQWtCLE1BQU0saUJBQWlCLENBQUM7QUFDdEQsT0FBTyxLQUFLLFdBQVcsTUFBTSxVQUFVLENBQUM7QUFDeEMsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLHdCQUF3QixNQUFNLHVCQUF1QixDQUFDO0FBQ2xFLE9BQU8sS0FBSyxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sS0FBSyxpQkFBaUIsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEtBQUssZ0JBQWdCLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sS0FBSyxhQUFhLE1BQU0sWUFBWSxD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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "31.3.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 = {
@@ -1154,6 +1169,91 @@ const registration: data.IClusterRelayRegistrationV1 = {
1154
1169
  const registrationReasons = data.validateClusterRelayRegistrationV1(registration); // []
1155
1170
  ```
1156
1171
 
1172
+ ### Cluster Ingress
1173
+
1174
+ A cluster's ingress terminates TLS for the hostnames its services publish and forwards to the
1175
+ host ports Pallet published for the workloads behind them. Cloudly computes what it serves — it
1176
+ is the only party that knows every service, every ready endpoint, the node address each published
1177
+ port answers on and the certificate each hostname needs — and the cluster relay carries that table
1178
+ to the ingress and the ingress's counters back. These contracts carry no version suffix and no
1179
+ schema field: the protocol version is the installed `@serve.zone/interfaces` version.
1180
+
1181
+ `IClusterRouteTable { revision, issuedAt, routes, portRoutes }` is the whole table, never a delta.
1182
+ `revision` is its only idempotency: a relay and an ingress apply a table whose revision is greater
1183
+ than the one they hold and ignore anything else, so a repeated push, a replayed push and a push
1184
+ that crossed a newer one all settle the same way. `IClusterHttpRoute` names one hostname, its
1185
+ `destinations` (`{ address, port }`, unicast IPv4 and a real port), the `certificate` that
1186
+ terminates it and optional Basic `authentication`; `IClusterPortRoute` forwards a TCP or UDP
1187
+ listen port without terminating anything. `validateClusterRouteTable(value, path?)` returns one
1188
+ named reason per violation, refuses two routes for one hostname and two port routes for one
1189
+ transport and port, and — because a validation reason travels into logs — **never quotes
1190
+ certificate material or a password in a reason**.
1191
+
1192
+ The table is secret-bearing end to end: it carries the private key of every hostname the ingress
1193
+ serves. Exclude it from hooks, logs, journals and disk on every hop, exactly as a runtime session
1194
+ credential is excluded.
1195
+
1196
+ | Method | Direction | Body |
1197
+ | --- | --- | --- |
1198
+ | `pushClusterRouteTable` | Cloudly → relay | `{ table }` → `{ appliedRevision }` |
1199
+ | `getClusterRouteTable` | relay → Cloudly | `{}` → `{ table \| null }` |
1200
+ | `registerClusterIngress` | ingress → relay → Cloudly | `{ bearer, version, nodeId, capabilities? }` → `{ accepted: true, revision \| null }` |
1201
+ | `getClusterIngressRouteTable` | ingress → relay | `{ knownRevision \| null }` → `{ table \| null }` |
1202
+ | `getClusterTrafficStatistics` | Cloudly → relay → ingress | `{ fromDayUtc, toDayUtc, cursor?, limit }` → `{ statistics, nextCursor? }` |
1203
+
1204
+ `null` for a table means Cloudly has computed none yet — a fact, not an empty table, and an
1205
+ ingress must not serve it as one. An ingress announces itself with a **method**, not a connection
1206
+ tag: the relay learns what connected from what it registered through, which a server owns, rather
1207
+ than from a label a client authored.
1208
+
1209
+ **Who may be the ingress is Cloudly's decision, not the relay's.** The ingress shares the relay's
1210
+ node listener, so anything that reaches it could ask to register, and a relay cannot tell an
1211
+ ingress from anything else. `registerClusterIngress` therefore carries the `bearer` Cloudly minted
1212
+ into the ingress workload's assignment environment and travels both hops with one body: the
1213
+ ingress sends it to its relay, the relay forwards it untouched, and Cloudly verifies the bearer
1214
+ and derives the cluster from the identity tag on the relay's own session. The bearer is sensitive
1215
+ ephemeral input — excluded from hooks, logs, diagnostics and journals on every hop, and never
1216
+ quoted in a validation reason.
1217
+
1218
+ Isolation on that shared listener is therefore by **authorization, not by routing**: only the
1219
+ connection whose registration Cloudly accepted is the ingress, `getClusterIngressRouteTable` and
1220
+ `getClusterTrafficStatistics` are answered for that connection alone, and any other connection —
1221
+ including a Pallet node's — is refused by name. `validateClusterIngressRegistration(value, path?)`
1222
+ checks shape only: whether the bearer is the one Cloudly minted is Cloudly's to verify.
1223
+
1224
+ `IClusterTrafficStatistics` is one UTC day of one hostname's traffic on one node —
1225
+ `requests`, `bytesIn`, `bytesOut`, `connections` and the backend failures the proxy can observe
1226
+ (`connect`, `handshake`, `request`). Day buckets rather than rates: a rate answers "how busy is it
1227
+ now", which the live proxy already answers, while a bucket survives a restart, a poll that
1228
+ straddles midnight and a Cloudly outage. HTTP status classes are deliberately absent — the proxy
1229
+ exposes no status counters today, and a number this contract cannot source would be a guess.
1230
+ Readers page it exactly like CoreMail's mail statistics — the same `fromDayUtc`, `toDayUtc`,
1231
+ `cursor` and `limit`, answered as `{ statistics, nextCursor? }` — re-reading yesterday and today on
1232
+ every pass because a day only stops changing once it has closed. `dayUtc` must name a day that
1233
+ exists: the shared UTC calendar-day rule round-trips through `Date.UTC`, so `2026-13-45` is
1234
+ refused rather than stored.
1235
+
1236
+ ```typescript
1237
+ const reasons = data.validateClusterRouteTable(table); // []
1238
+ const pageReasons = data.validateClusterTrafficStatisticsPage({ statistics: [] }); // []
1239
+ const ingressReasons = data.validateClusterIngressRegistration({
1240
+ bearer: ingressBearerFromAssignmentEnvironment,
1241
+ version: '3.0.0',
1242
+ nodeId: 'node-a',
1243
+ }); // []
1244
+ ```
1245
+
1246
+ ### Node Uplink Address
1247
+
1248
+ A route to a node's published host ports needs the address that node answers on, and only the
1249
+ node can prove it: it holds the uplink observation the address belongs to. It therefore states it
1250
+ on the network report it already sends — `IReportRuntimeNetworkProtectionRequestV1` gains the
1251
+ optional `uplinkAddress`, a unicast IPv4 literal that is neither loopback nor link-local
1252
+ (`runtimeNodeUplinkAddress`). Absence stays absent: a node that states no address is told apart
1253
+ from one that states a value, and nothing downstream defaults or infers one. Cloudly stores what
1254
+ the node stated and resolves it into the route table; the relay never substitutes an address of
1255
+ its own.
1256
+
1157
1257
  Shared service port helpers are exported from `data` so Cloudly, App Store resolution, Coreflow, Coretraffic, Onebox, and dcrouter agree on the same normalization rules:
1158
1258
 
1159
1259
  ```typescript
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '31.3.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