@serve.zone/interfaces 32.31.0 → 32.33.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.
@@ -87,7 +87,9 @@ export interface IRuntimeNetworkPublishedPort {
87
87
  * Outbound flows the workload originates from a published target port leave the node from the
88
88
  * matching host port, so a peer sees one address and port in both directions (SIP, RTP). Only
89
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`.
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`).
91
93
  */
92
94
  symmetric?: true;
93
95
  /**
@@ -115,7 +117,10 @@ export interface IRuntimeNetworkPublishedPortRequest {
115
117
  /** The last port of a range; `targetPort` then equals `hostPort`. */
116
118
  hostPortEnd?: number;
117
119
  targetPort: number;
118
- /** Same-port outbound; valid only while the service's policy document grants public egress. */
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
+ */
119
124
  symmetric?: true;
120
125
  }
121
126
 
@@ -132,7 +137,8 @@ export interface IServicePublishedPorts {
132
137
  /**
133
138
  * Sorted by protocol and host port, never overlapping, at most
134
139
  * `runtimeNetworkPublishedPortContract.maximumEntries` entries covering at most its
135
- * `maximumPortsPerService` ports. Empty means the service publishes nothing.
140
+ * `maximumPortsPerService` ports, and no symmetric entry sharing a target port with another
141
+ * entry of its protocol. Empty means the service publishes nothing.
136
142
  */
137
143
  publishedPorts: IRuntimeNetworkPublishedPortRequest[];
138
144
  }
@@ -235,7 +241,8 @@ export interface IRuntimeNetworkEndpoint {
235
241
  /**
236
242
  * Sorted by protocol and host port and never overlapping, within the bounds of
237
243
  * `runtimeNetworkPublishedPortContract`. Absent publishes nothing; a present member is never
238
- * empty. A `symmetric` entry is admitted only while `publicEgress` is true.
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.
239
246
  */
240
247
  publishedPorts?: IRuntimeNetworkPublishedPort[];
241
248
  }
@@ -414,6 +421,38 @@ export const isRuntimeNetworkPublishedPortReserved = (value: TPublishedPortSpan)
414
421
  value.hostPort < runtimeNetworkPublishedPortContract.publishablePorts.first
415
422
  || publishedPortLast(value) > runtimeNetworkPublishedPortContract.publishablePorts.last;
416
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
+
417
456
  /** The members every published-port entry shares; an optional member is stated only when present. */
418
457
  const publishedPortSpan = (item: Record<string, unknown>): IRuntimeNetworkPublishedPortRequest => {
419
458
  requireNetwork(item.protocol === 'tcp' || item.protocol === 'udp');
@@ -426,8 +465,11 @@ const publishedPortSpan = (item: Record<string, unknown>): IRuntimeNetworkPublis
426
465
  return { protocol: item.protocol, hostPort, ...(hasEnd ? { hostPortEnd } : {}), targetPort,
427
466
  ...(hasSymmetric ? { symmetric: true as const } : {}) };
428
467
  };
429
- /** Sorted, never overlapping within a protocol, and bounded in entries and in covered ports. */
430
- const publishedPortList = <T extends TPublishedPortSpan>(value: unknown, read: (item: unknown) => T): T[] => {
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[] => {
431
473
  const ports = networkSorted(networkList(value, runtimeNetworkPublishedPortContract.maximumEntries, read), publishedPortKey);
432
474
  let covered = 0;
433
475
  ports.forEach((port, index) => {
@@ -436,6 +478,7 @@ const publishedPortList = <T extends TPublishedPortSpan>(value: unknown, read: (
436
478
  covered += publishedPortLast(port) - port.hostPort + 1;
437
479
  });
438
480
  requireNetwork(covered <= runtimeNetworkPublishedPortContract.maximumPortsPerService);
481
+ requireNetwork(findRuntimeNetworkPublishedPortInsideOverlap(ports) === null);
439
482
  return ports;
440
483
  };
441
484
  const publishedPort = (value: unknown): IRuntimeNetworkPublishedPort => {
@@ -4,6 +4,10 @@ import type {
4
4
  IServiceMachineCredentialMetadata, IServiceMachineCredentialMutationResult,
5
5
  TServiceMachineCredentialMutation,
6
6
  } from '../data/servicecredential.js';
7
+ import type {
8
+ IDeploymentMachineUserMetadata, IDeploymentMachineUserMutationResult,
9
+ TDeploymentMachineUserMutation,
10
+ } from '../data/deploymentmachineuser.js';
7
11
 
8
12
  export interface IReq_Admin_GetServiceMachineCredential extends plugins.typedrequestInterfaces.implementsTR<
9
13
  plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredential
@@ -84,6 +88,39 @@ export interface IReq_Admin_LoginWithUsernameAndPassword extends plugins.typedre
84
88
  }
85
89
  }
86
90
 
91
+ /**
92
+ * Lists every deployment machine user, value-free: no token value is ever answered here.
93
+ * Requires an administrator; refusals travel as `IDeploymentMachineUserErrorData`.
94
+ */
95
+ export interface IReq_Admin_GetDeploymentMachineUsers extends plugins.typedrequestInterfaces.implementsTR<
96
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetDeploymentMachineUsers
97
+ > {
98
+ method: 'getDeploymentMachineUsers';
99
+ request: { identity: IIdentityCredential };
100
+ response: { users: IDeploymentMachineUserMetadata[] };
101
+ }
102
+
103
+ /**
104
+ * Creates a deployment machine user, or rotates or revokes its token.
105
+ *
106
+ * Requires an administrator. The token is answered exactly once, on the first answer to `create`
107
+ * or `rotate-token`; a replay of the same mutation ID by the same administrator answers the
108
+ * historical metadata with `token: null` and never issues another token. Grants are attached with
109
+ * `configureServiceDeploymentMachineUser`. Refusals travel as `IDeploymentMachineUserErrorData`.
110
+ */
111
+ export interface IReq_Admin_MutateDeploymentMachineUser extends plugins.typedrequestInterfaces.implementsTR<
112
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MutateDeploymentMachineUser
113
+ > {
114
+ method: 'mutateDeploymentMachineUser';
115
+ request: { identity: IIdentityCredential; mutation: TDeploymentMachineUserMutation };
116
+ response: IDeploymentMachineUserMutationResult;
117
+ }
118
+
119
+ /**
120
+ * Attaches one deployment grant to a deployment machine user that holds a token, replacing the
121
+ * grant it held for the same organization and service. `mutateDeploymentMachineUser` creates such
122
+ * a user.
123
+ */
87
124
  export interface IReq_Admin_ConfigureServiceDeploymentMachineUser
88
125
  extends plugins.typedrequestInterfaces.implementsTR<
89
126
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -231,6 +231,14 @@ extends plugins.typedrequestInterfaces.implementsTR<
231
231
  };
232
232
  }
233
233
 
234
+ /**
235
+ * Reserves one immutable deployment for the authenticated deployment machine user.
236
+ *
237
+ * Only an API machine user can complete the operation it reserves, because only its registry
238
+ * push is bound to that operation. An actor that is not an API machine user, an administrator
239
+ * included, is refused with the code `DEPLOYMENT_MACHINE_IDENTITY_REQUIRED` before anything is
240
+ * reserved.
241
+ */
234
242
  export interface IRequest_Any_Cloudly_ReserveServiceDeployment
235
243
  extends plugins.typedrequestInterfaces.implementsTR<
236
244
  plugins.typedrequestInterfaces.ITypedRequest,