@serve.zone/interfaces 32.30.0 → 32.32.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 +47 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/mail.d.ts +3 -3
- package/dist_ts/data/networkauthority.d.ts +14 -0
- package/dist_ts/data/networkauthority.js +12 -1
- package/dist_ts/data/networkprojection.d.ts +98 -11
- package/dist_ts/data/networkprojection.js +103 -21
- package/dist_ts/data/serviceruntime.d.ts +9 -1
- package/dist_ts/data/serviceruntime.js +2 -1
- package/package.json +1 -1
- package/readme.md +33 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/mail.ts +3 -3
- package/ts/data/networkauthority.ts +12 -0
- package/ts/data/networkprojection.ts +168 -31
- package/ts/data/serviceruntime.ts +9 -0
|
@@ -61,15 +61,37 @@ export interface IRuntimeNetworkWorkloadLease extends IRuntimeNetworkNodeScope {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* One node-level published port for a workload.
|
|
65
|
-
*
|
|
64
|
+
* One node-level published port, or one contiguous range of them, for a workload.
|
|
65
|
+
*
|
|
66
|
+
* Without `hostPortEnd` the entry publishes exactly `hostPort` to `targetPort`. With it the entry
|
|
67
|
+
* publishes every port from `hostPort` to `hostPortEnd` inclusive, each to the same port inside the
|
|
68
|
+
* workload, so `targetPort` equals `hostPort`. Every port an entry covers is claimed on its node:
|
|
69
|
+
* two entries of one node and protocol never share a port, and the entries of one endpoint never
|
|
70
|
+
* overlap and cover at most `runtimeNetworkPublishedPortContract.maximumPortsPerService` ports.
|
|
66
71
|
*/
|
|
67
72
|
export interface IRuntimeNetworkPublishedPort {
|
|
68
73
|
protocol: 'tcp' | 'udp';
|
|
69
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* 1..65535; the first port of a range. Cloudly publishes only ports inside
|
|
76
|
+
* `runtimeNetworkPublishedPortContract.publishablePorts` (`published-port-reserved`).
|
|
77
|
+
*/
|
|
70
78
|
hostPort: number;
|
|
71
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* The last port of a range, above `hostPort` and at most 65535. Omitted, the entry is one port;
|
|
81
|
+
* a one-port range is never written with it.
|
|
82
|
+
*/
|
|
83
|
+
hostPortEnd?: number;
|
|
84
|
+
/** The workload's own listening port inside its network namespace; `hostPort` for a range. */
|
|
72
85
|
targetPort: number;
|
|
86
|
+
/**
|
|
87
|
+
* Outbound flows the workload originates from a published target port leave the node from the
|
|
88
|
+
* matching host port, so a peer sees one address and port in both directions (SIP, RTP). Only
|
|
89
|
+
* `true` is ever stated; omitted, outbound flows keep the handoff lease's translation. It grants
|
|
90
|
+
* no egress: an endpoint carrying a symmetric entry has `publicEgress: true`. Its target ports
|
|
91
|
+
* are its own: no other entry of the list publishes to any of them
|
|
92
|
+
* (`findRuntimeNetworkPublishedPortInsideOverlap`).
|
|
93
|
+
*/
|
|
94
|
+
symmetric?: true;
|
|
73
95
|
/**
|
|
74
96
|
* Never the wildcard or loopback. Omitted means the node's uplink address.
|
|
75
97
|
* The contract cannot prove an uplink fact, so binding the port to that exact
|
|
@@ -84,15 +106,22 @@ export interface IRuntimeNetworkPublishedPort {
|
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
/**
|
|
87
|
-
* Caller-authored published-port request. It carries no `hostIp` (an omitted
|
|
88
|
-
*
|
|
89
|
-
* The `authorization` reference of the resulting projection entry is derived by
|
|
90
|
-
*
|
|
109
|
+
* Caller-authored published-port request. It carries no `hostIp` (an omitted address means the
|
|
110
|
+
* node uplink). `hostPortEnd` and `symmetric` follow the rules of `IRuntimeNetworkPublishedPort`.
|
|
111
|
+
* The `authorization` reference of the resulting projection entry is derived by Cloudly from the
|
|
112
|
+
* policy revision and digest, never supplied by the caller.
|
|
91
113
|
*/
|
|
92
114
|
export interface IRuntimeNetworkPublishedPortRequest {
|
|
93
115
|
protocol: 'tcp' | 'udp';
|
|
94
116
|
hostPort: number;
|
|
117
|
+
/** The last port of a range; `targetPort` then equals `hostPort`. */
|
|
118
|
+
hostPortEnd?: number;
|
|
95
119
|
targetPort: number;
|
|
120
|
+
/**
|
|
121
|
+
* Same-port outbound; valid only while the service's policy document grants public egress, and
|
|
122
|
+
* only on target ports no other entry of the list publishes to.
|
|
123
|
+
*/
|
|
124
|
+
symmetric?: true;
|
|
96
125
|
}
|
|
97
126
|
|
|
98
127
|
/**
|
|
@@ -105,13 +134,23 @@ export interface IServicePublishedPorts {
|
|
|
105
134
|
organizationId: string;
|
|
106
135
|
serviceId: string;
|
|
107
136
|
revision: number;
|
|
108
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Sorted by protocol and host port, never overlapping, at most
|
|
139
|
+
* `runtimeNetworkPublishedPortContract.maximumEntries` entries covering at most its
|
|
140
|
+
* `maximumPortsPerService` ports, and no symmetric entry sharing a target port with another
|
|
141
|
+
* entry of its protocol. Empty means the service publishes nothing.
|
|
142
|
+
*/
|
|
109
143
|
publishedPorts: IRuntimeNetworkPublishedPortRequest[];
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
/**
|
|
113
147
|
* Exact desired policy. Node-level host-port placement remains Cloudly's decision.
|
|
114
148
|
*
|
|
149
|
+
* Beyond the shape its snapshot checks, Cloudly refuses a port outside
|
|
150
|
+
* `runtimeNetworkPublishedPortContract.publishablePorts` and a symmetric entry while the document's
|
|
151
|
+
* `publicEgress` is false; `setServiceNetworkEgress` likewise refuses clearing `publicEgress` while a
|
|
152
|
+
* symmetric entry remains.
|
|
153
|
+
*
|
|
115
154
|
* One document per service holds its published ports and its egress under one `revision`. The
|
|
116
155
|
* first accepted write of either (`expectedRevision: null`) creates it: `setServicePublishedPorts`
|
|
117
156
|
* with egress `publicEgress: false` and `platformEndpointIds: []`, `setServiceNetworkEgress` with
|
|
@@ -160,7 +199,8 @@ export interface IServiceNetworkEgress {
|
|
|
160
199
|
|
|
161
200
|
/**
|
|
162
201
|
* Exact desired egress. The published ports of the same document are carried forward unchanged.
|
|
163
|
-
* Whether each id names a current platform endpoint is Cloudly's decision at acceptance
|
|
202
|
+
* Whether each id names a current platform endpoint is Cloudly's decision at acceptance, and so is
|
|
203
|
+
* the refusal of `publicEgress: false` while a carried published port is `symmetric`.
|
|
164
204
|
*
|
|
165
205
|
* One document per service holds its published ports and its egress under one `revision`. The
|
|
166
206
|
* first accepted write of either (`expectedRevision: null`) creates it: `setServicePublishedPorts`
|
|
@@ -198,7 +238,12 @@ export interface IRuntimeNetworkEndpoint {
|
|
|
198
238
|
/** TCP/UDP only; the complete protected union remains denied. */
|
|
199
239
|
publicEgress: boolean;
|
|
200
240
|
platformEndpointIds: string[];
|
|
201
|
-
/**
|
|
241
|
+
/**
|
|
242
|
+
* Sorted by protocol and host port and never overlapping, within the bounds of
|
|
243
|
+
* `runtimeNetworkPublishedPortContract`. Absent publishes nothing; a present member is never
|
|
244
|
+
* empty. A `symmetric` entry is admitted only while `publicEgress` is true and only on target
|
|
245
|
+
* ports no other entry of the endpoint publishes to.
|
|
246
|
+
*/
|
|
202
247
|
publishedPorts?: IRuntimeNetworkPublishedPort[];
|
|
203
248
|
}
|
|
204
249
|
|
|
@@ -286,6 +331,23 @@ export const runtimeNetworkProjectionContract = Object.freeze({
|
|
|
286
331
|
allocationReuse: 'requires-controller-quarantine-and-all-affected-authority-fences',
|
|
287
332
|
} as const);
|
|
288
333
|
|
|
334
|
+
/**
|
|
335
|
+
* The bounds of one service's published ports, the same on its policy document and on each of its
|
|
336
|
+
* projection endpoints.
|
|
337
|
+
*
|
|
338
|
+
* `maximumEntries` bounds the list and `maximumPortsPerService` the ports its entries cover
|
|
339
|
+
* together, a range counting every port in it; the snapshots refuse either excess.
|
|
340
|
+
* `publishablePorts` is Cloudly's placement rule, not a snapshot rule: the ports above it are
|
|
341
|
+
* `runtimeNetworkHandoffSnatPortRange`, which a handoff lease translates outbound flows to, so
|
|
342
|
+
* Cloudly refuses a policy with a port outside it as `published-port-reserved`
|
|
343
|
+
* (`isRuntimeNetworkPublishedPortReserved`). A policy written before this rule stays readable.
|
|
344
|
+
*/
|
|
345
|
+
export const runtimeNetworkPublishedPortContract = Object.freeze({
|
|
346
|
+
maximumEntries: 64,
|
|
347
|
+
maximumPortsPerService: 1024,
|
|
348
|
+
publishablePorts: Object.freeze({ first: 1, last: 49_151 } as const),
|
|
349
|
+
} as const);
|
|
350
|
+
|
|
289
351
|
/** The assignment contract owns the replica rule; a lease names the same slot by the same name. */
|
|
290
352
|
const replicaId = (value: unknown): string => {
|
|
291
353
|
requireNetwork(isRuntimeReplicaId(value)); return value as string;
|
|
@@ -346,28 +408,95 @@ const packetGrant = (value: unknown): IRuntimeNetworkPacketGrant => {
|
|
|
346
408
|
return { source: networkReference(item.source), destination: { kind, id: networkId(destination.id), address: networkIpv4(destination.address),
|
|
347
409
|
protocol: destination.protocol, port: networkCounter(destination.port, 65535) } };
|
|
348
410
|
};
|
|
349
|
-
|
|
411
|
+
type TPublishedPortSpan = Pick<IRuntimeNetworkPublishedPortRequest, 'protocol' | 'hostPort' | 'hostPortEnd'>;
|
|
412
|
+
const publishedPortKey = (value: TPublishedPortSpan): string =>
|
|
350
413
|
`${value.protocol}/${String(value.hostPort).padStart(5, '0')}`;
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
414
|
+
const publishedPortLast = (value: TPublishedPortSpan): number => value.hostPortEnd ?? value.hostPort;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Whether any port an entry covers lies outside `runtimeNetworkPublishedPortContract.publishablePorts`:
|
|
418
|
+
* Cloudly's `published-port-reserved`. It judges an entry its snapshot already accepted.
|
|
419
|
+
*/
|
|
420
|
+
export const isRuntimeNetworkPublishedPortReserved = (value: TPublishedPortSpan): boolean =>
|
|
421
|
+
value.hostPort < runtimeNetworkPublishedPortContract.publishablePorts.first
|
|
422
|
+
|| publishedPortLast(value) > runtimeNetworkPublishedPortContract.publishablePorts.last;
|
|
423
|
+
|
|
424
|
+
type TPublishedPortInside = Pick<IRuntimeNetworkPublishedPortRequest, 'protocol' | 'hostPort' | 'hostPortEnd' | 'targetPort' | 'symmetric'>;
|
|
425
|
+
/** The last workload port an entry publishes to; its inside span starts at `targetPort`. */
|
|
426
|
+
const publishedPortInsideLast = (value: TPublishedPortInside): number =>
|
|
427
|
+
value.targetPort + publishedPortLast(value) - value.hostPort;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* The first pair of one list in which a `symmetric` entry's inside ports (`targetPort` onwards, a
|
|
431
|
+
* range's every port) share a port with another entry of the same protocol, or `null`: Cloudly's
|
|
432
|
+
* `published-port-symmetric-inside-overlap`.
|
|
433
|
+
*
|
|
434
|
+
* A symmetric entry translates the flows its workload opens from those ports back to its own host
|
|
435
|
+
* ports, so no other entry of the workload may publish to any of them, symmetric or not: the flows
|
|
436
|
+
* would have two published sources. The lists of one service's policy document and of each of its
|
|
437
|
+
* projection endpoints are judged alike, and their snapshots refuse any pair. Distinct protocols
|
|
438
|
+
* never conflict. It judges entries their snapshot already accepted.
|
|
439
|
+
*/
|
|
440
|
+
export const findRuntimeNetworkPublishedPortInsideOverlap = <T extends TPublishedPortInside>(
|
|
441
|
+
ports: readonly T[],
|
|
442
|
+
): { symmetric: T; other: T } | null => {
|
|
443
|
+
for (let index = 0; index < ports.length; index++) {
|
|
444
|
+
const symmetric = ports[index];
|
|
445
|
+
if (symmetric.symmetric !== true) continue;
|
|
446
|
+
const first = symmetric.targetPort, last = publishedPortInsideLast(symmetric);
|
|
447
|
+
for (let other = 0; other < ports.length; other++) {
|
|
448
|
+
const candidate = ports[other];
|
|
449
|
+
if (other !== index && candidate.protocol === symmetric.protocol
|
|
450
|
+
&& candidate.targetPort <= last && first <= publishedPortInsideLast(candidate)) return { symmetric, other: candidate };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/** The members every published-port entry shares; an optional member is stated only when present. */
|
|
457
|
+
const publishedPortSpan = (item: Record<string, unknown>): IRuntimeNetworkPublishedPortRequest => {
|
|
354
458
|
requireNetwork(item.protocol === 'tcp' || item.protocol === 'udp');
|
|
355
|
-
const
|
|
459
|
+
const hostPort = networkCounter(item.hostPort, 65535), targetPort = networkCounter(item.targetPort, 65535);
|
|
460
|
+
const hasEnd = Object.hasOwn(item, 'hostPortEnd'), hasSymmetric = Object.hasOwn(item, 'symmetric');
|
|
461
|
+
const hostPortEnd = hasEnd ? networkCounter(item.hostPortEnd, 65535) : hostPort;
|
|
462
|
+
// A range maps each host port to the same workload port; a one-port range has one encoding, none.
|
|
463
|
+
requireNetwork(!hasEnd || (hostPortEnd > hostPort && targetPort === hostPort));
|
|
464
|
+
requireNetwork(!hasSymmetric || item.symmetric === true);
|
|
465
|
+
return { protocol: item.protocol, hostPort, ...(hasEnd ? { hostPortEnd } : {}), targetPort,
|
|
466
|
+
...(hasSymmetric ? { symmetric: true as const } : {}) };
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
469
|
+
* Sorted, never overlapping within a protocol, bounded in entries and in covered ports, and no
|
|
470
|
+
* symmetric entry sharing an inside port with another entry.
|
|
471
|
+
*/
|
|
472
|
+
const publishedPortList = <T extends TPublishedPortInside>(value: unknown, read: (item: unknown) => T): T[] => {
|
|
473
|
+
const ports = networkSorted(networkList(value, runtimeNetworkPublishedPortContract.maximumEntries, read), publishedPortKey);
|
|
474
|
+
let covered = 0;
|
|
475
|
+
ports.forEach((port, index) => {
|
|
476
|
+
const previous = index === 0 ? null : ports[index - 1];
|
|
477
|
+
requireNetwork(previous === null || previous.protocol !== port.protocol || publishedPortLast(previous) < port.hostPort);
|
|
478
|
+
covered += publishedPortLast(port) - port.hostPort + 1;
|
|
479
|
+
});
|
|
480
|
+
requireNetwork(covered <= runtimeNetworkPublishedPortContract.maximumPortsPerService);
|
|
481
|
+
requireNetwork(findRuntimeNetworkPublishedPortInsideOverlap(ports) === null);
|
|
482
|
+
return ports;
|
|
483
|
+
};
|
|
484
|
+
const publishedPort = (value: unknown): IRuntimeNetworkPublishedPort => {
|
|
485
|
+
const item = networkRecord(value, ['protocol', 'hostPort', 'targetPort', 'authorization'], ['hostIp', 'hostPortEnd', 'symmetric']);
|
|
486
|
+
const { symmetric, ...span } = publishedPortSpan(item);
|
|
487
|
+
const hostIp = Object.hasOwn(item, 'hostIp') ? networkIpv4(item.hostIp) : null;
|
|
356
488
|
requireNetwork(hostIp === null || (hostIp !== '0.0.0.0' && !networkContains('127.0.0.0/8', `${hostIp}/32`)));
|
|
357
|
-
return {
|
|
358
|
-
|
|
489
|
+
return { ...span, ...(hostIp === null ? {} : { hostIp }), ...(symmetric ? { symmetric } : {}),
|
|
490
|
+
authorization: networkReference(item.authorization) };
|
|
359
491
|
};
|
|
360
492
|
const publishedPorts = (value: unknown): IRuntimeNetworkPublishedPort[] => {
|
|
361
|
-
const ports =
|
|
493
|
+
const ports = publishedPortList(value, publishedPort);
|
|
362
494
|
requireNetwork(ports.length > 0); return ports;
|
|
363
495
|
};
|
|
364
|
-
const publishedPortRequest = (value: unknown): IRuntimeNetworkPublishedPortRequest =>
|
|
365
|
-
|
|
366
|
-
requireNetwork(item.protocol === 'tcp' || item.protocol === 'udp');
|
|
367
|
-
return { protocol: item.protocol, hostPort: networkCounter(item.hostPort, 65535), targetPort: networkCounter(item.targetPort, 65535) };
|
|
368
|
-
};
|
|
496
|
+
const publishedPortRequest = (value: unknown): IRuntimeNetworkPublishedPortRequest =>
|
|
497
|
+
publishedPortSpan(networkRecord(value, ['protocol', 'hostPort', 'targetPort'], ['hostPortEnd', 'symmetric']));
|
|
369
498
|
const publishedPortRequests = (value: unknown): IRuntimeNetworkPublishedPortRequest[] =>
|
|
370
|
-
|
|
499
|
+
publishedPortList(value, publishedPortRequest);
|
|
371
500
|
|
|
372
501
|
/** Structural snapshot only; node placement, quota and authorization remain Cloudly's decision. */
|
|
373
502
|
export const snapshotServicePublishedPorts = (value: unknown): IServicePublishedPorts => networkSafe(() => {
|
|
@@ -420,9 +549,12 @@ const endpoint = (value: unknown): IRuntimeNetworkEndpoint => {
|
|
|
420
549
|
reporterSessionId: networkId(observed.reporterSessionId), sequence: networkCounter(observed.sequence), digest: networkDigest(observed.digest) };
|
|
421
550
|
}
|
|
422
551
|
requireNetwork(!item.ready || (observation !== null && observation.assignment.generation === 1));
|
|
552
|
+
const ports = hasPublishedPorts ? publishedPorts(item.publishedPorts) : null;
|
|
553
|
+
// Same-port outbound rides the public egress grant; it never grants egress of its own.
|
|
554
|
+
requireNetwork(item.publicEgress || !(ports ?? []).some(port => port.symmetric));
|
|
423
555
|
return { lease, ready: item.ready, observation, publicEgress: item.publicEgress,
|
|
424
556
|
platformEndpointIds: networkSorted(networkList(item.platformEndpointIds, 64, networkId), value => value),
|
|
425
|
-
...(
|
|
557
|
+
...(ports === null ? {} : { publishedPorts: ports }) };
|
|
426
558
|
};
|
|
427
559
|
|
|
428
560
|
/** Structural snapshot only; use validate/signature/admission helpers before accepting authority. */
|
|
@@ -527,7 +659,8 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
|
|
|
527
659
|
requireNetwork(!aliases.has(name)); aliases.add(name);
|
|
528
660
|
}
|
|
529
661
|
}
|
|
530
|
-
const assignments = new Set<string>(), attempts = new Set<string>(), readyReplicas = new Set<string>()
|
|
662
|
+
const assignments = new Set<string>(), attempts = new Set<string>(), readyReplicas = new Set<string>();
|
|
663
|
+
const hostPortClaims: Array<{ node: string; first: number; last: number }> = [];
|
|
531
664
|
for (const endpoint of projection.endpoints) {
|
|
532
665
|
const lease = endpoint.lease, authority = allocationAuthority(lease.protectedAuthority);
|
|
533
666
|
// A controller on the node itself has no other node: every endpoint it projects is local.
|
|
@@ -546,12 +679,16 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
|
|
|
546
679
|
pool.id === lease.ipv4.poolId && pool.purpose === 'workload' && networkContains(pool.prefix, lease.ipv4.linkSubnet)));
|
|
547
680
|
if (networkSameScope(lease, projection)) requireNetwork(projection.handoff !== null && lease.routerIncarnationId === projection.handoff.routerIncarnationId);
|
|
548
681
|
for (const id of endpoint.platformEndpointIds) requireNetwork(current.platformEndpoints.some(platform => platform.id === id));
|
|
549
|
-
for (const port of endpoint.publishedPorts ?? []) {
|
|
550
|
-
|
|
551
|
-
requireNetwork(!hostPorts.has(claim)); hostPorts.add(claim);
|
|
552
|
-
}
|
|
682
|
+
for (const port of endpoint.publishedPorts ?? []) hostPortClaims.push({ node: networkCanonical([lease.nodeId, port.protocol]),
|
|
683
|
+
first: port.hostPort, last: publishedPortLast(port) });
|
|
553
684
|
requireNetwork(!projection.retiredLeases.some(retired => networkEqual(retired, networkReferenceOf(lease))));
|
|
554
685
|
}
|
|
686
|
+
// Every port of every entry is claimed on its node: ordered by start, no claim reaches the next one.
|
|
687
|
+
hostPortClaims.sort((left, right) => left.node < right.node ? -1 : left.node > right.node ? 1 : left.first - right.first);
|
|
688
|
+
for (let index = 1; index < hostPortClaims.length; index++) {
|
|
689
|
+
const previous = hostPortClaims[index - 1], current = hostPortClaims[index];
|
|
690
|
+
requireNetwork(previous.node !== current.node || previous.last < current.first);
|
|
691
|
+
}
|
|
555
692
|
for (const id of projection.hostPlatformEndpointIds ?? []) requireNetwork(current.platformEndpoints.some(platform => platform.id === id));
|
|
556
693
|
for (let index = 0; index < projection.endpoints.length; index++) for (let previous = 0; previous < index; previous++) {
|
|
557
694
|
requireNetwork(!networkOverlaps(projection.endpoints[index].lease.ipv4.linkSubnet, projection.endpoints[previous].lease.ipv4.linkSubnet));
|
|
@@ -91,6 +91,7 @@ const serviceRuntimeRefusals = [
|
|
|
91
91
|
'volume-placement-unsupported', 'volume-initialization-unknown', 'volume-ownership-unknown',
|
|
92
92
|
'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
|
|
93
93
|
'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
|
|
94
|
+
'published-port-reserved', 'published-port-symmetric-without-egress',
|
|
94
95
|
] as const;
|
|
95
96
|
|
|
96
97
|
/**
|
|
@@ -149,6 +150,14 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
|
|
|
149
150
|
* or an IPv6 literal is never selected. Each is judged only for an endpoint that is valid: a node
|
|
150
151
|
* stating none, or an invalid one, stays `database-endpoint-missing` or `objectstorage-endpoint-missing`.
|
|
151
152
|
* `platform.findUnselectedCorestoreEndpoints` is this rule; a producer answers what it returns.
|
|
153
|
+
*
|
|
154
|
+
* The two `published-port-` refusals name a service policy document whose published ports no
|
|
155
|
+
* projection may carry, so its endpoints are not compiled rather than published on some ports only.
|
|
156
|
+
* `published-port-reserved` is an entry covering a port outside
|
|
157
|
+
* `runtimeNetworkPublishedPortContract.publishablePorts` (`isRuntimeNetworkPublishedPortReserved`),
|
|
158
|
+
* which the handoff leases' `runtimeNetworkHandoffSnatPortRange` owns; a document written before
|
|
159
|
+
* that rule can hold one. `published-port-symmetric-without-egress` is a `symmetric` entry in a
|
|
160
|
+
* document whose `publicEgress` is false, which a projection endpoint refuses.
|
|
152
161
|
*/
|
|
153
162
|
export type TServiceRuntimeRefusal = typeof serviceRuntimeRefusals[number];
|
|
154
163
|
|