@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.
package/changelog.md CHANGED
@@ -1,5 +1,77 @@
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
+
28
+ ## 2026-09-16 - 31.4.0
29
+
30
+ ### Features
31
+
32
+ - Add the cluster ingress contracts, the first under the rule that a contract carries no version
33
+ suffix and no schema field: the protocol version is the installed package version.
34
+ `IClusterRouteTable { revision, issuedAt, routes, portRoutes }` is the whole table a cluster's
35
+ ingress serves, with `IClusterHttpRoute` (hostname, unicast destinations, the certificate that
36
+ terminates it, optional Basic authentication) and `IClusterPortRoute` (a TCP or UDP listen port
37
+ forwarded untouched). `revision` is the feed's only idempotency: a relay and an ingress apply a
38
+ greater revision and ignore anything else, so a repeated, replayed or crossed push settles the
39
+ same way. `validateClusterRouteTable` returns one named reason per violation, refuses a second
40
+ route for one hostname and a second port route for one transport and port, and never quotes
41
+ certificate material or a password in a reason — a reason travels into logs, and this table
42
+ carries the private key of every hostname the ingress serves.
43
+ - Add the five methods the feed and its counters travel on: `pushClusterRouteTable` (Cloudly →
44
+ relay), `getClusterRouteTable` (relay → Cloudly), `getClusterIngressRouteTable` (ingress →
45
+ relay), `registerClusterIngress` and `getClusterTrafficStatistics` (both one body for two hops:
46
+ ingress → relay → Cloudly, and Cloudly → relay → ingress). An ingress announces itself through a
47
+ method rather than a connection tag, so what a relay knows about its ingress is what a server
48
+ owns. A `null` table means Cloudly has computed none yet — a fact, not an empty table.
49
+ - Authorize the ingress where the authority is. The ingress shares the relay's node listener, so
50
+ anything reaching it could ask to register and a relay cannot tell an ingress from anything
51
+ else: `registerClusterIngress` carries the `bearer` Cloudly minted into the ingress workload's
52
+ assignment environment, the relay forwards it untouched, and Cloudly verifies it and derives the
53
+ cluster from the identity tag on the relay's own session. The bearer is sensitive ephemeral
54
+ input on every hop and is never quoted in a reason. Isolation on the shared listener is by
55
+ authorization, not routing: only the connection whose registration Cloudly accepted is the
56
+ ingress, and every other connection is refused by name.
57
+ `validateClusterIngressRegistration` checks shape only.
58
+ - Add `IClusterTrafficStatistics`: one UTC day of one hostname's traffic on one node — requests,
59
+ bytes in and out, connections and the backend connect, handshake and request failures the proxy
60
+ can observe — paged exactly like CoreMail's mail statistics, down to the `{ statistics,
61
+ nextCursor? }` response and the `fromDayUtc`/`toDayUtc`/`cursor`/`limit` request, with `dayUtc`
62
+ checked against the real calendar through the now-shared UTC calendar-day rule (`2026-13-45` is
63
+ refused rather than stored; CoreMail's own day check consumes the same helper). Day buckets survive a restart, a
64
+ poll across midnight and a Cloudly outage, which a rate does not. HTTP status classes are
65
+ deliberately absent: the proxy exposes no status counters today, and a number the contract
66
+ cannot source would be a guess.
67
+ - Carry the node's own uplink address on the network report it already sends:
68
+ `IReportRuntimeNetworkProtectionRequestV1` gains the optional `uplinkAddress`, validated by
69
+ `runtimeNodeUplinkAddress` as a unicast IPv4 literal that is neither loopback nor link-local.
70
+ A route to that node's published ports uses exactly this address, and only the node can prove
71
+ it. Absence stays absent, so a node that states none is told apart from one that states a value,
72
+ and nothing downstream defaults or infers one. Additive: `networkRecord` now admits declared
73
+ optional keys, and every existing caller passes none.
74
+
3
75
  ## 2026-09-16 - 31.3.0
4
76
 
5
77
  ### Features
@@ -3,7 +3,7 @@
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
  };
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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
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The cluster ingress contracts.
3
+ *
4
+ * A cluster's ingress terminates TLS for the hostnames its services publish and
5
+ * forwards to the host ports Pallet published for the workloads behind them.
6
+ * Cloudly computes the table — it is the only party that knows every service,
7
+ * every ready endpoint and every certificate — and the relay carries it to the
8
+ * ingress and the ingress's counters back.
9
+ *
10
+ * Names here carry no version suffix and no schema field: the protocol version
11
+ * is the installed `@serve.zone/interfaces` version.
12
+ */
13
+ export declare const clusterIngressLimits: Readonly<{
14
+ minimumIngressBearerLength: 16;
15
+ maximumIngressBearerLength: 4096;
16
+ maximumHostNameLength: 253;
17
+ maximumRoutes: 4096;
18
+ maximumPortRoutes: 1024;
19
+ maximumDestinationsPerRoute: 64;
20
+ maximumStatisticsPageSize: 1000;
21
+ maximumCursorLength: 512;
22
+ maximumIdentifierLength: 200;
23
+ maximumIngressCapabilities: 32;
24
+ }>;
25
+ /** One backend a route forwards to: an address a node answers on, and its port. */
26
+ export interface IClusterRouteDestination {
27
+ address: string;
28
+ port: number;
29
+ }
30
+ /** Basic authentication for a route, as the proxy has always expressed it. */
31
+ export interface IClusterRouteAuthentication {
32
+ type: 'Basic';
33
+ user: string;
34
+ pass: string;
35
+ }
36
+ /**
37
+ * One hostname the ingress serves.
38
+ *
39
+ * `certificate` carries the private key the ingress needs to terminate TLS for
40
+ * `hostName`. A route table is therefore secret-bearing end to end: it is never
41
+ * logged, never journaled, never written to disk, and no validation reason
42
+ * derived from it ever quotes it.
43
+ */
44
+ export interface IClusterHttpRoute {
45
+ hostName: string;
46
+ destinations: IClusterRouteDestination[];
47
+ certificate: {
48
+ publicKey: string;
49
+ privateKey: string;
50
+ };
51
+ authentication?: IClusterRouteAuthentication;
52
+ }
53
+ /** One TCP or UDP port the ingress forwards without terminating anything. */
54
+ export interface IClusterPortRoute {
55
+ name: string;
56
+ listenPort: number;
57
+ transport: 'tcp' | 'udp';
58
+ destinations: IClusterRouteDestination[];
59
+ }
60
+ /**
61
+ * Everything a cluster's ingress serves, as one table.
62
+ *
63
+ * `revision` is the only idempotency the feed has: an ingress and a relay apply
64
+ * a table whose revision is greater than the one they hold and ignore anything
65
+ * else, so a repeated push, a replayed push and a push that crossed a newer one
66
+ * all settle the same way.
67
+ */
68
+ export interface IClusterRouteTable {
69
+ revision: number;
70
+ issuedAt: number;
71
+ routes: IClusterHttpRoute[];
72
+ portRoutes: IClusterPortRoute[];
73
+ }
74
+ /**
75
+ * One day of one hostname's traffic on one node, as the ingress counted it.
76
+ *
77
+ * Day buckets rather than rates: a rate answers "how busy is it now", which the
78
+ * live proxy already answers, while a bucket survives a restart, a poll that
79
+ * straddles midnight and a Cloudly outage.
80
+ */
81
+ export interface IClusterTrafficStatistics {
82
+ serviceId: string;
83
+ hostName: string;
84
+ nodeId: string;
85
+ /** UTC calendar day, `YYYY-MM-DD`. */
86
+ dayUtc: string;
87
+ requests: number;
88
+ bytesIn: number;
89
+ bytesOut: number;
90
+ connections: number;
91
+ /**
92
+ * What the proxy could observe about its backends. HTTP status classes are
93
+ * deliberately absent: the proxy exposes no status counters today, and a
94
+ * number this contract cannot source would be a guess.
95
+ */
96
+ backendErrors: {
97
+ connect: number;
98
+ handshake: number;
99
+ request: number;
100
+ };
101
+ updatedAt: number;
102
+ }
103
+ /**
104
+ * A hostname a certificate can be issued for and a browser can present.
105
+ *
106
+ * Lowercase labels only, because a certificate names the lowercase form and a
107
+ * table that carried both would serve one of them without a certificate.
108
+ */
109
+ export declare const isClusterIngressHostName: (valueArg: unknown) => valueArg is string;
110
+ /**
111
+ * Validates one route table and names every reason it is refused.
112
+ *
113
+ * Shape only. Whether a hostname belongs to the cluster, whether a certificate
114
+ * matches it, and whether a destination is a node of this cluster are decisions
115
+ * for the party that computed the table; a table that passes here has claimed
116
+ * none of them. No reason ever quotes certificate material.
117
+ */
118
+ export declare const validateClusterRouteTable: (valueArg: unknown, pathArg?: string) => string[];
119
+ /** Validates one day bucket of ingress counters. */
120
+ export declare const validateClusterTrafficStatistics: (valueArg: unknown, pathArg?: string) => string[];
121
+ /**
122
+ * Validates one page of ingress counters, as a reader receives it.
123
+ *
124
+ * The cursor is opaque to everyone but the ingress that issued it: bounded and
125
+ * printable here, meaningful only there.
126
+ */
127
+ export declare const validateClusterTrafficStatisticsPage: (valueArg: unknown, pathArg?: string) => string[];
128
+ /**
129
+ * Validates one cluster ingress registration and names every reason it is refused.
130
+ *
131
+ * Shape only, and deliberately so: whether the bearer is the one Cloudly minted
132
+ * for this cluster's ingress is Cloudly's to verify against its own records, and
133
+ * which cluster is speaking comes from the identity tag on the relay's session.
134
+ * A registration that passes here has proven nothing — and no reason it returns
135
+ * ever quotes the bearer, because a reason travels into logs.
136
+ */
137
+ export declare const validateClusterIngressRegistration: (valueArg: unknown, pathArg?: string) => string[];