@serve.zone/interfaces 32.64.0 → 32.66.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.
@@ -0,0 +1,113 @@
1
+ import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
2
+ import type { IRuntimeNetworkDnsRecordSet, IRuntimeNetworkDnsRecordSets, IRuntimeNetworkDnsView, IRuntimeNetworkEndpoint,
3
+ IRuntimeNetworkProjection } from './networkprojection.js';
4
+ import type { IPrivateNetwork, IServicePrivateNetworks } from './privatenetwork.js';
5
+ import { buildPrivateNetworkFqdn, composeServicePrivateNetworkDnsPolicy } from './privatenetwork.js';
6
+ import { networkCanonical, networkEqual, networkReferenceOf, networkSameScope, requireNetwork } from '../private/networkcontract.js';
7
+
8
+ const ownerKey = (value: { organizationId: string; serviceId: string }): string => networkCanonical([value.organizationId, value.serviceId]);
9
+
10
+ /**
11
+ * Internal DNS composition after validation. The public composers validate the whole projection
12
+ * first; these read only the validated projection and the captured selection, never an attachment
13
+ * journal, so their answers are a pure function of both.
14
+ *
15
+ * The local endpoints a composition serves, in projection order: every local endpoint, or exactly
16
+ * the selected ones, each named once and each local.
17
+ */
18
+ export const dnsSourcesOf = (projection: IRuntimeNetworkProjection,
19
+ selected: readonly IRuntimeAuthorityReference[] | null): IRuntimeNetworkEndpoint[] => {
20
+ const local = projection.endpoints.filter(endpoint => networkSameScope(endpoint.lease, projection));
21
+ if (selected !== null) {
22
+ requireNetwork(new Set(selected.map(source => source.id)).size === selected.length);
23
+ for (const source of selected) requireNetwork(local.some(endpoint => networkEqual(networkReferenceOf(endpoint.lease), source)));
24
+ }
25
+ return local.filter(endpoint => selected === null || selected.some(source => networkEqual(networkReferenceOf(endpoint.lease), source)));
26
+ };
27
+
28
+ /**
29
+ * Each membership's ready replica addresses, sorted, read once per projection: one pass over the
30
+ * endpoints, so a name's A set costs a lookup instead of a scan of every endpoint (O(M+E), not
31
+ * O(M*E)). A membership without a ready replica has none and its names are declared NODATA.
32
+ */
33
+ export const readyAddressesOf = (projection: IRuntimeNetworkProjection): Map<string, string[]> => {
34
+ const ready = new Map<string, string[]>();
35
+ for (const { ready: isReady, lease } of projection.endpoints) {
36
+ if (!isReady) continue;
37
+ const key = ownerKey(lease);
38
+ let addresses = ready.get(key);
39
+ if (!addresses) ready.set(key, addresses = []);
40
+ addresses.push(lease.ipv4.address);
41
+ }
42
+ for (const addresses of ready.values()) addresses.sort();
43
+ return ready;
44
+ };
45
+
46
+ const policyOf = (projection: IRuntimeNetworkProjection, networks: Map<string, IPrivateNetwork>, member: IServicePrivateNetworks) =>
47
+ composeServicePrivateNetworkDnsPolicy(member, member.attachments.map(attachment => networks.get(attachment.networkId)),
48
+ projection.protectedAuthority.resolvers);
49
+ const byName = (left: { name: string }, right: { name: string }) => left.name < right.name ? -1 : 1;
50
+
51
+ /** One union view per source (`composeRuntimeNetworkProjectionDnsViews`). */
52
+ export const dnsViewsOf = (projection: IRuntimeNetworkProjection,
53
+ selected: readonly IRuntimeAuthorityReference[] | null): IRuntimeNetworkDnsView[] => {
54
+ const sources = dnsSourcesOf(projection, selected);
55
+ const networks = new Map(projection.networks.map(network => [network.id, network]));
56
+ const members = new Map(projection.memberships.map(member => [ownerKey(member), member]));
57
+ const ready = readyAddressesOf(projection);
58
+ return sources.map(({ lease }) => {
59
+ const member = members.get(ownerKey(lease))!, { organizationId, attachments } = member;
60
+ const policy = policyOf(projection, networks, member), attached = new Set(attachments.map(own => own.networkId));
61
+ const records: IRuntimeNetworkDnsView['records'] = [];
62
+ for (const target of projection.memberships) {
63
+ if (target.organizationId !== organizationId) continue;
64
+ const addresses = ready.get(ownerKey(target)) ?? [];
65
+ for (const attachment of target.attachments) {
66
+ if (!attached.has(attachment.networkId)) continue;
67
+ for (const alias of attachment.aliases) records.push({ name: buildPrivateNetworkFqdn(networks.get(attachment.networkId), alias), addresses: [...addresses] });
68
+ }
69
+ }
70
+ records.sort(byName);
71
+ return { source: networkReferenceOf(lease), sourceAddress: lease.ipv4.address, dnsServer: lease.dnsServer, policy, records };
72
+ });
73
+ };
74
+
75
+ /**
76
+ * One record set per network a selected source attaches, and per source its view without records
77
+ * (`composeRuntimeNetworkProjectionDnsRecordSets`). A network's set holds every alias of every
78
+ * membership attaching it, which the projection's validation confines to the network's own
79
+ * organization, so the union of a source's sets is its union view's records.
80
+ */
81
+ export const dnsRecordSetsOf = (projection: IRuntimeNetworkProjection,
82
+ selected: readonly IRuntimeAuthorityReference[] | null): IRuntimeNetworkDnsRecordSets => {
83
+ const sources = dnsSourcesOf(projection, selected);
84
+ const networks = new Map(projection.networks.map(network => [network.id, network]));
85
+ const members = new Map(projection.memberships.map(member => [ownerKey(member), member]));
86
+ const suffixes = new Map<string, string>();
87
+ const views = sources.map(({ lease }) => {
88
+ const policy = policyOf(projection, networks, members.get(ownerKey(lease))!);
89
+ for (const network of policy.networkSuffixes) suffixes.set(network.networkId, network.dnsSuffix);
90
+ return { source: networkReferenceOf(lease), sourceAddress: lease.ipv4.address, dnsServer: lease.dnsServer,
91
+ policy, networkIds: policy.networkSuffixes.map(network => network.networkId) };
92
+ });
93
+ // Only the networks a selected source references, in network id order (the projection's order).
94
+ const sets = new Map<string, IRuntimeNetworkDnsRecordSet>();
95
+ for (const network of projection.networks) {
96
+ const dnsSuffix = suffixes.get(network.id);
97
+ if (dnsSuffix !== undefined) sets.set(network.id, { networkId: network.id, dnsSuffix, records: [] });
98
+ }
99
+ if (sets.size > 0) {
100
+ const ready = readyAddressesOf(projection);
101
+ for (const target of projection.memberships) {
102
+ for (const attachment of target.attachments) {
103
+ const set = sets.get(attachment.networkId);
104
+ if (!set) continue;
105
+ const addresses = ready.get(ownerKey(target)) ?? [];
106
+ for (const alias of attachment.aliases) set.records.push({ name: buildPrivateNetworkFqdn(networks.get(attachment.networkId), alias), addresses: [...addresses] });
107
+ }
108
+ }
109
+ }
110
+ const recordSets = [...sets.values()];
111
+ for (const set of recordSets) set.records.sort(byName);
112
+ return { recordSets, views };
113
+ };
@@ -10,6 +10,7 @@ import { routerPacketGrantsOf, snapshotRouterProjection,
10
10
  import { hostPacketGrantsOf, snapshotHostProjection,
11
11
  type IRuntimeNetworkHostPacketGrant, type IRuntimeNetworkHostProjection } from './networkhost.js';
12
12
  import { privateMeshEndpointsOf, type IRuntimeNetworkPrivateMesh } from './networkprivatemesh.js';
13
+ import { dnsRecordSetsOf, dnsViewsOf } from './networkdnscomposition.js';
13
14
  import { snapshotWorkloadIngressProjection, workloadIngressPacketGrantsOf,
14
15
  type IRuntimeNetworkWorkloadIngressPacketGrant, type IRuntimeNetworkWorkloadIngressProjection } from './networkworkloadingress.js';
15
16
  import { requireFirstProjectionRetirement, requireProjectionRetirement, snapshotRuntimeNetworkProjectionRetirement,
@@ -323,6 +324,42 @@ export interface IRuntimeNetworkDnsView {
323
324
  records: Array<{ name: string; addresses: string[] }>;
324
325
  }
325
326
 
327
+ /**
328
+ * The DNS records of one private network, defined once for every source that attaches it
329
+ * (`composeRuntimeNetworkProjectionDnsRecordSets`).
330
+ */
331
+ export interface IRuntimeNetworkDnsRecordSet {
332
+ networkId: string;
333
+ /** The network's zone, exactly its `IServicePrivateNetworkDnsPolicy.networkSuffixes` entry. */
334
+ dnsSuffix: string;
335
+ /**
336
+ * Every alias of every membership attaching the network, sorted by name, with its complete
337
+ * ready-replica A set; [] is declared NODATA, as in `IRuntimeNetworkDnsView.records`.
338
+ */
339
+ records: IRuntimeNetworkDnsView['records'];
340
+ }
341
+
342
+ /** One selected source: its union view without records, and the record sets it serves instead. */
343
+ export interface IRuntimeNetworkDnsSetView {
344
+ source: IRuntimeAuthorityReference;
345
+ sourceAddress: string;
346
+ dnsServer: string;
347
+ policy: IServicePrivateNetworkDnsPolicy;
348
+ /**
349
+ * The `networkId` of each record set this source serves: its attached networks, in
350
+ * `policy.networkSuffixes` order. The union of those sets' records is exactly the records of its
351
+ * `IRuntimeNetworkDnsView`; no name is in two of them.
352
+ */
353
+ networkIds: string[];
354
+ }
355
+
356
+ export interface IRuntimeNetworkDnsRecordSets {
357
+ /** One per network some selected source attaches, and no other, sorted by network id. */
358
+ recordSets: IRuntimeNetworkDnsRecordSet[];
359
+ /** One per selected local source, in projection order, as `composeRuntimeNetworkProjectionDnsViews` answers. */
360
+ views: IRuntimeNetworkDnsSetView[];
361
+ }
362
+
326
363
  export const runtimeNetworkProjectionContract = Object.freeze({
327
364
  workloadDigestDomain: 'serve.zone/runtime-network-workload-lease',
328
365
  projectionDigestDomain: 'serve.zone/runtime-network-projection',
@@ -763,31 +800,24 @@ export const composeRuntimeNetworkProjectionDnsViews = async (
763
800
  value: unknown, sourcesValue?: readonly IRuntimeAuthorityReference[],
764
801
  ): Promise<IRuntimeNetworkDnsView[]> => {
765
802
  const selected = sourcesValue === undefined ? null : networkList(captureNetwork(sourcesValue), 256, networkReference);
766
- const projection = await validateRuntimeNetworkProjection(value);
767
- const local = projection.endpoints.filter(endpoint => networkSameScope(endpoint.lease, projection));
768
- if (selected !== null) {
769
- requireNetwork(new Set(selected.map(source => source.id)).size === selected.length);
770
- for (const source of selected) requireNetwork(local.some(endpoint => networkEqual(networkReferenceOf(endpoint.lease), source)));
771
- }
772
- const networks = new Map(projection.networks.map(network => [network.id, network]));
773
- const members = new Map(projection.memberships.map(member => [ownerKey(member), member]));
774
- return local.filter(endpoint => selected === null || selected.some(source => networkEqual(networkReferenceOf(endpoint.lease), source))).map(endpoint => {
775
- const member = members.get(ownerKey(endpoint.lease))!;
776
- const policy = composeServicePrivateNetworkDnsPolicy(member,
777
- member.attachments.map(attachment => networks.get(attachment.networkId)), projection.protectedAuthority.resolvers);
778
- const records: IRuntimeNetworkDnsView['records'] = [];
779
- for (const target of projection.memberships) {
780
- if (target.organizationId !== member.organizationId) continue;
781
- const addresses = projection.endpoints.filter(candidate => candidate.ready && ownerKey(candidate.lease) === ownerKey(target))
782
- .map(candidate => candidate.lease.ipv4.address).sort();
783
- for (const attachment of target.attachments) {
784
- if (!member.attachments.some(own => own.networkId === attachment.networkId)) continue;
785
- for (const alias of attachment.aliases) records.push({ name: buildPrivateNetworkFqdn(networks.get(attachment.networkId), alias), addresses: [...addresses] });
786
- }
787
- }
788
- records.sort((left, right) => left.name < right.name ? -1 : 1);
789
- return { source: networkReferenceOf(endpoint.lease), sourceAddress: endpoint.lease.ipv4.address, dnsServer: endpoint.lease.dnsServer, policy, records };
790
- });
803
+ return dnsViewsOf(await validateRuntimeNetworkProjection(value), selected);
804
+ };
805
+
806
+ /**
807
+ * The same DNS material with each network's records defined once: one record set per private
808
+ * network a selected local source attaches, and per source its view without records, naming its
809
+ * networks' sets in `policy.networkSuffixes` order. A source's sets together hold exactly the
810
+ * records of its `composeRuntimeNetworkProjectionDnsViews` view, so the material grows with sources
811
+ * plus names rather than with their product. Selection, validation and refusals are those of
812
+ * `composeRuntimeNetworkProjectionDnsViews`; the answer is a pure function of the validated
813
+ * projection and the selection, in a stable order. It names no consumer's identifiers: a consumer
814
+ * derives its own set ids.
815
+ */
816
+ export const composeRuntimeNetworkProjectionDnsRecordSets = async (
817
+ value: unknown, sourcesValue?: readonly IRuntimeAuthorityReference[],
818
+ ): Promise<IRuntimeNetworkDnsRecordSets> => {
819
+ const selected = sourcesValue === undefined ? null : networkList(captureNetwork(sourcesValue), 256, networkReference);
820
+ return dnsRecordSetsOf(await validateRuntimeNetworkProjection(value), selected);
791
821
  };
792
822
 
793
823
  /** Bind immutable address material without a circular assignment-digest dependency. */
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import type * as data from '../data/index.js';
3
3
  import type { IIdentity, IIdentityCredential } from '../data/user.js';
4
+ import { hostedAppStatusMessageMaximumLength } from '../data/hostedapp.js';
4
5
  import type {
5
6
  IHostedAppBootstrapAction,
6
7
  IHostedAppBootstrapActionRequest,
@@ -211,7 +212,8 @@ export const validateHostedAppCompleteBootstrapActionRequest = (
211
212
  errors.push('hosted app bootstrap completion must fence ready status');
212
213
  }
213
214
  if (request.message !== undefined
214
- && (typeof request.message !== 'string' || request.message.length > 4000)) {
215
+ && (typeof request.message !== 'string'
216
+ || request.message.length > hostedAppStatusMessageMaximumLength)) {
215
217
  errors.push('hosted app bootstrap completion message must be bounded');
216
218
  }
217
219
  return errors;