@serve.zone/interfaces 31.8.0 → 31.9.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.
Files changed (40) hide show
  1. package/changelog.md +45 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/clusterruntime.d.ts +39 -0
  4. package/dist_ts/data/clusterruntime.js +11 -0
  5. package/dist_ts/data/index.d.ts +4 -0
  6. package/dist_ts/data/index.js +5 -1
  7. package/dist_ts/data/networkaddressplan.d.ts +78 -0
  8. package/dist_ts/data/networkaddressplan.js +114 -0
  9. package/dist_ts/data/networkauthority.js +5 -17
  10. package/dist_ts/data/networknode.d.ts +37 -0
  11. package/dist_ts/data/networknode.js +6 -0
  12. package/dist_ts/data/runtimeconfig.js +14 -77
  13. package/dist_ts/data/serviceruntime.d.ts +128 -0
  14. package/dist_ts/data/serviceruntime.js +92 -0
  15. package/dist_ts/private/networkcontract.d.ts +4 -0
  16. package/dist_ts/private/networkcontract.js +15 -1
  17. package/dist_ts/private/producercontract.d.ts +11 -0
  18. package/dist_ts/private/producercontract.js +21 -0
  19. package/dist_ts/private/runtimepolicy.d.ts +31 -0
  20. package/dist_ts/private/runtimepolicy.js +90 -0
  21. package/dist_ts/requests/cluster.d.ts +37 -0
  22. package/dist_ts/requests/cluster.js +1 -1
  23. package/dist_ts/requests/network.d.ts +57 -1
  24. package/dist_ts/requests/service.d.ts +60 -0
  25. package/package.json +1 -1
  26. package/readme.md +120 -0
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/data/clusterruntime.ts +45 -0
  29. package/ts/data/index.ts +4 -0
  30. package/ts/data/networkaddressplan.ts +175 -0
  31. package/ts/data/networkauthority.ts +4 -16
  32. package/ts/data/networknode.ts +37 -0
  33. package/ts/data/runtimeconfig.ts +17 -76
  34. package/ts/data/serviceruntime.ts +208 -0
  35. package/ts/private/networkcontract.ts +14 -0
  36. package/ts/private/producercontract.ts +19 -0
  37. package/ts/private/runtimepolicy.ts +105 -0
  38. package/ts/requests/cluster.ts +45 -0
  39. package/ts/requests/network.ts +50 -1
  40. package/ts/requests/service.ts +81 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The execution policy a runtime config seals and a service runtime profile states ahead of it.
3
+ * Both validators apply these timing, HTTP request, CPU, memory, identity and log rules. Only the
4
+ * service's target ports decide whether a readiness target exists, which a profile cannot know.
5
+ *
6
+ * Every check here reads captured plain data only, the output of `snapshotBoundedJson` or
7
+ * `captureNetwork`: on live input a getter could pass a check and then hand the caller another value.
8
+ */
9
+ export const runtimePolicyLimits = Object.freeze({
10
+ minimumMemoryBytes: 16 * 1024 * 1024,
11
+ maximumMemoryBytes: 2 ** 50,
12
+ maximumCpuMillis: 1_000_000,
13
+ maximumIdentityNumber: 2 ** 31 - 1,
14
+ maximumReadinessDurationMs: 3_600_000,
15
+ maximumLogRetentionMs: 31_536_000_000,
16
+ });
17
+ export const isBoundedInteger = (valueArg, minimumArg, maximumArg) => typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && !Object.is(valueArg, -0)
18
+ && valueArg >= minimumArg && valueArg <= maximumArg;
19
+ export const isRuntimePortName = (valueArg) => typeof valueArg === 'string' && /^[a-z0-9][a-z0-9_.-]{0,62}$/.test(valueArg);
20
+ export const isExactRecord = (valueArg, keysArg) => {
21
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg))
22
+ return false;
23
+ const keys = Object.keys(valueArg);
24
+ return keys.length === keysArg.length && keys.every((keyArg) => keysArg.includes(keyArg));
25
+ };
26
+ /** null explicitly requests no CPU quota; zero is never a quota. */
27
+ export const isRuntimeCpuMillis = (valueArg) => valueArg === null || isBoundedInteger(valueArg, 1, runtimePolicyLimits.maximumCpuMillis);
28
+ export const isRuntimeMemoryBytes = (valueArg) => isBoundedInteger(valueArg, runtimePolicyLimits.minimumMemoryBytes, runtimePolicyLimits.maximumMemoryBytes);
29
+ export const isRuntimeIdentityNumber = (valueArg) => isBoundedInteger(valueArg, 0, runtimePolicyLimits.maximumIdentityNumber);
30
+ export const isRuntimeLogPolicy = (valueArg) => isExactRecord(valueArg, ['maximumLineBytes', 'maximumBufferedBytes', 'retentionMs'])
31
+ && isBoundedInteger(valueArg.maximumLineBytes, 1, 1_048_576)
32
+ && isBoundedInteger(valueArg.maximumBufferedBytes, 1, 67_108_864)
33
+ && valueArg.maximumBufferedBytes >= valueArg.maximumLineBytes
34
+ && isBoundedInteger(valueArg.retentionMs, 1, runtimePolicyLimits.maximumLogRetentionMs);
35
+ const timingKeys = ['initialDelayMs', 'startupGraceMs', 'intervalMs', 'timeoutMs',
36
+ 'successThreshold', 'failureThreshold'];
37
+ /**
38
+ * Readiness policy. `selectsTcpTargetArg` decides whether a probe's named target and port exist:
39
+ * a config answers from its own target ports, a profile ahead of any config cannot know them.
40
+ */
41
+ export const assertRuntimeReadiness = (valueArg, selectsTcpTargetArg, failArg) => {
42
+ if (!valueArg || typeof valueArg !== 'object')
43
+ failArg('runtime config readiness is required');
44
+ const value = valueArg;
45
+ const keys = value.kind === 'process' ? ['kind', ...timingKeys]
46
+ : value.kind === 'tcp' ? ['kind', ...timingKeys, 'targetPort', 'port']
47
+ : value.kind === 'http' ? ['kind', ...timingKeys, 'targetPort', 'port',
48
+ 'scheme', 'host', 'path', 'expectedStatusCodes', 'followRedirects'] : [];
49
+ const maximumDurationMs = runtimePolicyLimits.maximumReadinessDurationMs;
50
+ if (keys.length === 0 || !isExactRecord(value, keys)
51
+ || !isBoundedInteger(value.initialDelayMs, 0, maximumDurationMs)
52
+ || !isBoundedInteger(value.startupGraceMs, 0, maximumDurationMs)
53
+ || !isBoundedInteger(value.intervalMs, 100, maximumDurationMs)
54
+ || !isBoundedInteger(value.timeoutMs, 1, 60_000)
55
+ || !isBoundedInteger(value.successThreshold, 1, 100)
56
+ || !isBoundedInteger(value.failureThreshold, 1, 100)
57
+ || value.timeoutMs > value.intervalMs || value.initialDelayMs > value.startupGraceMs) {
58
+ failArg('runtime config readiness must use explicit bounded timing and exact schema');
59
+ }
60
+ if (value.kind === 'process')
61
+ return;
62
+ if (!isRuntimePortName(value.targetPort) || !isBoundedInteger(value.port, 1, 65535)
63
+ || !selectsTcpTargetArg(value.targetPort, value.port)) {
64
+ failArg('runtime config readiness must select a declared TCP target and port');
65
+ }
66
+ if (value.kind === 'tcp')
67
+ return;
68
+ if ((value.scheme !== 'http' && value.scheme !== 'https') || value.followRedirects !== false
69
+ || typeof value.host !== 'string' || value.host.length > 253
70
+ || !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/.test(value.host)
71
+ || typeof value.path !== 'string' || value.path.length > 4096
72
+ || !value.path.startsWith('/') || value.path.startsWith('//')
73
+ || /[^\x21-\x7e]|[\\#]/.test(value.path)
74
+ || /%(?![0-9A-F]{2})/.test(value.path)
75
+ || !Array.isArray(value.expectedStatusCodes) || value.expectedStatusCodes.length === 0
76
+ || value.expectedStatusCodes.length > 32) {
77
+ failArg('runtime config HTTP readiness must use an exact bounded request policy');
78
+ }
79
+ const parsed = new URL(value.path, 'http://runtime.invalid');
80
+ if (`${parsed.pathname}${parsed.search}` !== value.path)
81
+ failArg('runtime config HTTP path must be canonical');
82
+ let previousCode = 0;
83
+ for (const code of value.expectedStatusCodes) {
84
+ if (!isBoundedInteger(code, 200, 599) || code <= previousCode) {
85
+ failArg('runtime config HTTP status expectations must be sorted unique final codes');
86
+ }
87
+ previousCode = code;
88
+ }
89
+ };
90
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZXBvbGljeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3ByaXZhdGUvcnVudGltZXBvbGljeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUMvQyxrQkFBa0IsRUFBRSxFQUFFLEdBQUcsSUFBSSxHQUFHLElBQUk7SUFDcEMsa0JBQWtCLEVBQUUsQ0FBQyxJQUFJLEVBQUU7SUFDM0IsZ0JBQWdCLEVBQUUsU0FBUztJQUMzQixxQkFBcUIsRUFBRSxDQUFDLElBQUksRUFBRSxHQUFHLENBQUM7SUFDbEMsMEJBQTBCLEVBQUUsU0FBUztJQUNyQyxxQkFBcUIsRUFBRSxjQUFjO0NBQzdCLENBQUMsQ0FBQztBQUlaLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsUUFBaUIsRUFBRSxVQUFrQixFQUFFLFVBQWtCLEVBQXNCLEVBQUUsQ0FDaEgsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQztPQUN2RixRQUFRLElBQUksVUFBVSxJQUFJLFFBQVEsSUFBSSxVQUFVLENBQUM7QUFDdEQsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxRQUFpQixFQUFzQixFQUFFLENBQ3pFLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSw2QkFBNkIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDL0UsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLENBQzNCLFFBQWlCLEVBQUUsT0FBMEIsRUFDUixFQUFFO0lBQ3ZDLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxD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
@@ -1,5 +1,7 @@
1
1
  import * as userInterfaces from '../data/user.js';
2
2
  import * as clusterInterfaces from '../data/cluster.js';
3
+ import type { IClusterRuntime, TClusterRuntimeBlocker } from '../data/clusterruntime.js';
4
+ import type { IServiceRuntimeAssignmentView } from '../data/serviceruntime.js';
3
5
  import type * as leaseInterfaces from '../data/networklease.js';
4
6
  import * as plugins from '../plugins.js';
5
7
  /**
@@ -141,3 +143,38 @@ export interface IReq_Any_Cloudly_DeleteClusterById extends plugins.typedrequest
141
143
  ok: boolean;
142
144
  };
143
145
  }
146
+ /**
147
+ * A cluster's runtime phase, and what still blocks it from leaving `swarm`.
148
+ *
149
+ * Cloudly refuses a cluster without a phase record as `cluster-runtime-missing` rather than
150
+ * assuming one. `blockers` is empty once nothing blocks the cutover.
151
+ */
152
+ export interface IReq_Any_Cloudly_GetClusterRuntime extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetClusterRuntime> {
153
+ method: 'getClusterRuntime';
154
+ request: {
155
+ identity: userInterfaces.IIdentity;
156
+ clusterId: string;
157
+ };
158
+ response: {
159
+ runtime: IClusterRuntime;
160
+ blockers: TClusterRuntimeBlocker[];
161
+ };
162
+ }
163
+ /**
164
+ * Every current runtime assignment on one cluster's nodes, a page at a time: a cluster carries
165
+ * every node's slots of every service, which no single response bounds. `limit` runs from 1 to
166
+ * `serviceRuntimeContract.maximumAssignmentPageSize`; Cloudly refuses any other.
167
+ */
168
+ export interface IReq_Any_Cloudly_GetClusterRuntimeAssignments extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetClusterRuntimeAssignments> {
169
+ method: 'getClusterRuntimeAssignments';
170
+ request: {
171
+ identity: userInterfaces.IIdentity;
172
+ clusterId: string;
173
+ cursor: string | null;
174
+ limit: number;
175
+ };
176
+ response: {
177
+ assignments: IServiceRuntimeAssignmentView[];
178
+ nextCursor: string | null;
179
+ };
180
+ }
@@ -1,4 +1,4 @@
1
1
  import * as userInterfaces from '../data/user.js';
2
2
  import * as clusterInterfaces from '../data/cluster.js';
3
3
  import * as plugins from '../plugins.js';
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlcXVlc3RzL2NsdXN0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLGNBQWMsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sb0JBQW9CLENBQUM7QUFFeEQsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUMifQ==
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlcXVlc3RzL2NsdXN0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLGNBQWMsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sb0JBQW9CLENBQUM7QUFJeEQsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUMifQ==
@@ -1,5 +1,7 @@
1
1
  import * as plugins from '../plugins.js';
2
- import type { IIdentityCredential } from '../data/user.js';
2
+ import type { IIdentity, IIdentityCredential } from '../data/user.js';
3
+ import type { IRuntimeNetworkAddressPlan, TRuntimeNetworkAddressPlanWritableData } from '../data/networkaddressplan.js';
4
+ import type { IRuntimeNetworkNodeReadiness, IRuntimeRouterIncarnation } from '../data/networknode.js';
3
5
  import type { IPrivateNetworkV1, IServicePrivateNetworksV1, ISetServicePrivateNetworksV1, TPrivateNetworkMutationV1 } from '../data/privatenetwork.js';
4
6
  import type { IServicePublishedPortsV1, ISetServicePublishedPortsV1 } from '../data/networkprojection.js';
5
7
  /** Current organization picker data. Entries are never cached authorization grants. */
@@ -130,3 +132,57 @@ export interface IRequest_Any_Cloudly_GetNetworkNodes {
130
132
  networkNodes: plugins.tsclass.network.INetworkNode[];
131
133
  };
132
134
  }
135
+ /**
136
+ * Writes the runtime network's address plan.
137
+ *
138
+ * `expectedRevision` is the revision the caller read, or `null` when no plan exists yet. Cloudly
139
+ * validates the plan with `snapshotRuntimeNetworkAddressPlan`, decides the change with
140
+ * `admitRuntimeNetworkAddressPlanChange`, refuses the change while a resolver or platform endpoint
141
+ * it removes or changes is still referenced, and answers with the stored plan: at its new revision,
142
+ * or unchanged.
143
+ */
144
+ export interface IReq_Any_Cloudly_SetRuntimeNetworkAddressPlan extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_SetRuntimeNetworkAddressPlan> {
145
+ method: 'setRuntimeNetworkAddressPlan';
146
+ request: {
147
+ identity: IIdentity;
148
+ expectedRevision: number | null;
149
+ plan: TRuntimeNetworkAddressPlanWritableData;
150
+ };
151
+ response: {
152
+ plan: IRuntimeNetworkAddressPlan;
153
+ };
154
+ }
155
+ /** `null` until an administrator sets the first plan; the runtime network stays unconfigured until then. */
156
+ export interface IReq_Any_Cloudly_GetRuntimeNetworkAddressPlan extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetRuntimeNetworkAddressPlan> {
157
+ method: 'getRuntimeNetworkAddressPlan';
158
+ request: {
159
+ identity: IIdentity;
160
+ };
161
+ response: {
162
+ plan: IRuntimeNetworkAddressPlan | null;
163
+ };
164
+ }
165
+ /** Whether one node's runtime network is ready for workloads, and every condition still missing. */
166
+ export interface IReq_Any_Cloudly_GetRuntimeNetworkNodeReadiness extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetRuntimeNetworkNodeReadiness> {
167
+ method: 'getRuntimeNetworkNodeReadiness';
168
+ request: {
169
+ identity: IIdentity;
170
+ nodeId: string;
171
+ };
172
+ response: IRuntimeNetworkNodeReadiness;
173
+ }
174
+ /**
175
+ * Advances one node's router incarnation, so the node producer reserves the router a fresh handoff.
176
+ * `expectedIncarnation` is the incarnation the caller read; any other refuses the request.
177
+ */
178
+ export interface IReq_Any_Cloudly_ReincarnateRuntimeNetworkRouter extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_ReincarnateRuntimeNetworkRouter> {
179
+ method: 'reincarnateRuntimeNetworkRouter';
180
+ request: {
181
+ identity: IIdentity;
182
+ nodeId: string;
183
+ expectedIncarnation: number;
184
+ };
185
+ response: {
186
+ incarnation: IRuntimeRouterIncarnation;
187
+ };
188
+ }
@@ -8,6 +8,7 @@ import type { IImageRelease } from '../data/immutableimage.js';
8
8
  import type { IDeploymentPreflightReport, IDeploymentPreflightRequestData } from '../data/deploymentpreflight.js';
9
9
  import type { IDeploymentReservationInput, IServiceDeploymentOperation, IServiceDeploymentStatus } from '../data/deploymentoperation.js';
10
10
  import type { TSha256Digest } from '../data/immutableimage.js';
11
+ import type { IServiceRuntimeAssignmentView, IServiceRuntimeSpec, IServiceRuntimeStatus, TServiceRuntimeSpecWritableData } from '../data/serviceruntime.js';
11
12
  import type { IServiceDeploymentAdoptionInput, IServiceDeploymentAdoptionImagePromotionInput, IServiceDeploymentAdoptionImagePromotionResult, IServiceDeploymentAdoptionRolloutInput, IServiceDeploymentAdoptionRolloutResult, IServiceDeploymentAdoptionPreflightInput, IServiceDeploymentAdoptionReport, IServiceDeploymentAdoptionResult } from '../data/deploymentadoption.js';
12
13
  export interface IRequest_Any_Cloudly_BootstrapServiceDeploymentAdoptionRollout extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_BootstrapServiceDeploymentAdoptionRollout> {
13
14
  method: 'bootstrapServiceDeploymentAdoptionRollout';
@@ -253,3 +254,62 @@ export interface IRequest_Any_Cloudly_DeleteServiceById extends plugins.typedreq
253
254
  success: boolean;
254
255
  };
255
256
  }
257
+ /**
258
+ * Writes the complete runtime spec of one service.
259
+ *
260
+ * `expectedRevision` is the revision the caller read, or `null` when the service has never had a
261
+ * spec; any other stored revision refuses the write. Cloudly copies the organization from the
262
+ * service and answers with the stored spec at its new revision.
263
+ */
264
+ export interface IRequest_Any_Cloudly_SetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_SetServiceRuntimeSpec> {
265
+ method: 'setServiceRuntimeSpec';
266
+ request: {
267
+ identity: IIdentity;
268
+ serviceId: string;
269
+ expectedRevision: number | null;
270
+ spec: TServiceRuntimeSpecWritableData;
271
+ };
272
+ response: {
273
+ spec: IServiceRuntimeSpec;
274
+ };
275
+ }
276
+ /**
277
+ * A service's runtime spec and the producer's verdict on it. Both are `null` before the first spec;
278
+ * a spec's status is `null` until the producer first decides it.
279
+ */
280
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeSpec> {
281
+ method: 'getServiceRuntimeSpec';
282
+ request: {
283
+ identity: IIdentity;
284
+ serviceId: string;
285
+ };
286
+ response: {
287
+ spec: null;
288
+ status: null;
289
+ } | {
290
+ spec: IServiceRuntimeSpec;
291
+ status: IServiceRuntimeStatus | null;
292
+ };
293
+ }
294
+ /** Every current runtime assignment of one service, one per replica slot. */
295
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeAssignments extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeAssignments> {
296
+ method: 'getServiceRuntimeAssignments';
297
+ request: {
298
+ identity: IIdentity;
299
+ serviceId: string;
300
+ };
301
+ response: {
302
+ assignments: IServiceRuntimeAssignmentView[];
303
+ };
304
+ }
305
+ /**
306
+ * Cloudly -> admin UIs: a service's runtime spec, status or assignments changed. It carries no
307
+ * state, so a late or repeated push can never overwrite a newer read; the UI reads the views again.
308
+ */
309
+ export interface IReq_Cloudly_Any_PushServiceRuntimeChanged extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Any_PushServiceRuntimeChanged> {
310
+ method: 'pushServiceRuntimeChanged';
311
+ request: {
312
+ serviceId: string;
313
+ };
314
+ response: Record<string, never>;
315
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "31.8.0",
3
+ "version": "31.9.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
@@ -594,6 +594,62 @@ const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenew
594
594
  const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);
595
595
  ```
596
596
 
597
+ ### Runtime Network Address Plan
598
+
599
+ `data.IRuntimeNetworkAddressPlan` is the address space an administrator gives the runtime network,
600
+ and the single input Cloudly composes the protected authority from:
601
+ `{ id: 'address-plan', revision, prefixes, pools, resolvers, platformEndpoints, vpn: { controlPrefix, relay } }`.
602
+ Pools, resolvers and platform endpoints use the protected authority's own element shapes, and the
603
+ relay uses the managed-VPN credential's `{ id, address, port }`. `platformEndpoints` never lists the
604
+ relay: the composed authority adds it, in id order, as the UDP platform endpoint every node dials.
605
+
606
+ `snapshotRuntimeNetworkAddressPlan` refuses any plan that could describe an authority the authority
607
+ contract refuses. It runs `snapshotRuntimeNetworkProtectedAuthorityV1` twice, once over the plan's
608
+ own sets and once with the relay added, so overlapping pools, more than 16 resolvers, a resolver or
609
+ endpoint outside `prefixes` or inside a pool, and a relay that `prefixes` do not cover are all
610
+ refused by that one validator. On top it requires the VPN `controlPrefix` to be a private prefix no
611
+ longer than /30 (`runtimeNetworkAddressPlanContract.maximumControlPrefixLength`) inside `prefixes`
612
+ that overlaps no pool and holds no resolver or platform endpoint, the relay included. No minimum
613
+ length is stated: the private-range rule already keeps it at /8 or longer.
614
+ `composeRuntimeNetworkProtectedAuthority(plan, frame)` is the composition itself: the producer
615
+ supplies the frame (`TRuntimeNetworkProtectedAuthorityFrame`: authority id, generation, controller,
616
+ previous reference and egress authorities) and receives the complete authority with its digest, so
617
+ the validator and the protection producer can never compose differently.
618
+
619
+ `admitRuntimeNetworkAddressPlanChange(previous | null, next)` is the pure decision for a plan write at
620
+ the following revision; any other revision is a contract violation. Leases and the managed VPN
621
+ outlive a plan revision, so it answers:
622
+
623
+ - `unchanged` when the content equals the stored plan; Cloudly writes nothing;
624
+ - `refused` with `plan-vpn-immutable` when the VPN differs, `plan-pool-removed` when any pool is gone
625
+ or changed its purpose or prefix, and `plan-prefix-shrunk` when the new prefixes, however split,
626
+ no longer cover every address the old ones did;
627
+ - `accepted` with the ids of the resolvers and platform endpoints the change removes or changes. An
628
+ entry that keeps its id but differs in any field counts, so moving it goes through the same
629
+ reference check as removing it.
630
+
631
+ `TRuntimeNetworkAddressPlanRefusal` adds `plan-resolver-referenced` and `plan-endpoint-referenced`,
632
+ which only Cloudly can decide against live references, so every plan refusal has one type.
633
+ `requests.network` carries `setRuntimeNetworkAddressPlan { identity, expectedRevision, plan }`
634
+ (`expectedRevision: null` when no plan exists yet) and `getRuntimeNetworkAddressPlan`, which answers
635
+ `null` until the first plan is set.
636
+
637
+ ### Node Network Readiness
638
+
639
+ `requests.network.getRuntimeNetworkNodeReadiness { identity, nodeId }` answers
640
+ `data.IRuntimeNetworkNodeReadiness`: `ready: true` with nothing missing, or `ready: false` with at
641
+ least one `TRuntimeNetworkNodeReadinessGap`. The gaps follow the node producer's order: the node
642
+ joins the protected authority's egress (`egress-pending`), every egress owner reports current
643
+ protection (`protection-receipts-pending`, naming those `owners`, at least one), a handoff is reserved
644
+ (`handoff-pending`), the router selection carries it (`selection-pending`), the node's acknowledged
645
+ projection carries it (`projection-pending`), and the node is a managed VPN member
646
+ (`vpn-member-pending`).
647
+
648
+ `data.IRuntimeRouterIncarnation { id, incarnation }` is a node's router incarnation, from which its
649
+ handoff and router incarnation ids derive; `snapshotRuntimeRouterIncarnation` checks the record.
650
+ `reincarnateRuntimeNetworkRouter { identity, nodeId, expectedIncarnation }` advances it, so the node
651
+ producer reserves the router a fresh handoff, and answers with the new incarnation.
652
+
597
653
  ### Node-Bound Runtime Assignments
598
654
 
599
655
  The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
@@ -727,6 +783,70 @@ only the named Pallet attempt. They do not authorize storage relocation, prove
727
783
  whole-host or Docker/Coreflow absence, grant another controller or organization
728
784
  authority, or replace independently qualified readiness and routing evidence.
729
785
 
786
+ ### Cluster Runtime Phase
787
+
788
+ `data.IClusterRuntime { id, phase, generation, changedAt, changedBy }` records which runtime owns a
789
+ cluster's workloads: `TClusterRuntimePhase` is `swarm`, `draining`, `switching` or `pallet`. It is
790
+ stored beside the cluster document, because a cluster update merges caller data and no caller may
791
+ move a phase with it; `generation` is its compare-and-swap counter. `snapshotClusterRuntime` checks
792
+ the record. Which phase may follow which belongs to the cutover, whose requests ship with it.
793
+
794
+ `requests.cluster.getClusterRuntime { identity, clusterId }` answers `{ runtime, blockers }`. Each
795
+ `TClusterRuntimeBlocker` names what it is about: cluster-wide blockers (`relay-not-enabled`,
796
+ `relay-not-registered`, `address-plan-missing`, `vpn-unconfigured`) name nothing, node blockers
797
+ (`node-without-pallet-credential`, `node-arch-unsupported`) name the `nodeId`, and service blockers
798
+ (`service-multi-cluster-scope`, `service-authority-unsupported`, `platform-binding-present`) name
799
+ the `serviceId`.
800
+
801
+ ### Service Runtime Specs And Status
802
+
803
+ `data.IServiceRuntimeSpec` is what Cloudly runs for one service on a Pallet cluster:
804
+ `{ id, organizationId, clusterId, placement, replicasPerNode, hold, profile, network, revision }`.
805
+ It is stored complete and beside the service document, so a hold written by a cutover never races
806
+ an operator's service edit.
807
+
808
+ - `placement` is `{ kind: 'every-node' }` or `{ kind: 'nodes', nodeIds }`, with `nodeIds` sorted,
809
+ unique, never empty and at most `maximumPinnedNodes`, the protected authority's egress limit.
810
+ - `replicasPerNode` runs from 1 to 64; Cloudly refuses a service desiring more than `maximumReplicas` (64) as `replica-limit`.
811
+ - `profile { cpuMillis, memoryBytes, readiness, logs, readonlyRootfs, runAs }` is the runtime config's
812
+ execution policy ahead of any image: the same readiness and log shapes, and the same CPU, memory
813
+ (at least 16 MiB), identity, log, readiness timing and HTTP request rules, which both validators
814
+ share. `runAs: null` runs the image's own OCI user. Only the service's target ports decide whether
815
+ a readiness probe's target exists: the runtime config requires the service to declare it, and the
816
+ producer refuses a service that does not as `readiness-target-undeclared`.
817
+ - `network` is `{ mode: 'isolated' }` or `{ mode: 'attached', publicEgress, platformEndpointIds }`
818
+ with sorted unique endpoint ids.
819
+
820
+ `snapshotServiceRuntimeSpec` checks all of that; whether the cluster, the nodes and the endpoints
821
+ exist is Cloudly's. `requests.service.setServiceRuntimeSpec { identity, serviceId, expectedRevision, spec }`
822
+ writes `TServiceRuntimeSpecWritableData`, and Cloudly copies the organization from the service.
823
+ `getServiceRuntimeSpec` answers `{ spec, status }`: both `null` before the first spec, and `status`
824
+ `null` until the producer first decides the spec. A status never comes without its spec.
825
+
826
+ `data.IServiceRuntimeStatus { id, specRevision, state, reason, changedAt }` is the producer's verdict
827
+ on one spec revision, and each reason belongs to exactly one state. `converged` and `converging`
828
+ carry `reason: null`. `waiting` carries a `TServiceRuntimeWaitingReason`: `node-network-pending`,
829
+ `conflict` or `failure-backoff`. `refused` carries a `TServiceRuntimeRefusal`:
830
+ `cluster-runtime-missing`, `organization-missing`, `image-plan-missing`, `platform-unsupported`,
831
+ `secrets-unsupported`, `volumes-unsupported`, `port-protocol-unsupported`,
832
+ `readiness-target-undeclared`, `environment-too-large`, `no-eligible-node` or `replica-limit`. A
833
+ refusal never retires running slots. `snapshotServiceRuntimeStatus` enforces the pairing.
834
+
835
+ ### Runtime Assignment Views
836
+
837
+ `data.IServiceRuntimeAssignmentView` is one replica slot's current assignment as an administrator
838
+ reads it: `serviceId`, `replicaId`, `nodeId`, the current `assignment` reference, its `disposition`,
839
+ the latest accepted `observation { phase, ready, observedAt, sequence }` (which may cite an earlier
840
+ revision), `terminalReceiptCount` (0 to 2) and `delivery`. `delivered` means the node's own evidence,
841
+ an observation or a terminal receipt, cites the current revision; until then it is `undelivered`.
842
+
843
+ `requests.service.getServiceRuntimeAssignments { identity, serviceId }` answers every slot of one
844
+ service. `requests.cluster.getClusterRuntimeAssignments { identity, clusterId, cursor, limit }` pages
845
+ through every slot on a cluster's nodes; `limit` runs from 1 to
846
+ `serviceRuntimeContract.maximumAssignmentPageSize` (256), and Cloudly refuses any other.
847
+ `requests.service.pushServiceRuntimeChanged { serviceId }` tells admin UIs that a service's spec,
848
+ status or assignments changed; it carries no state, so a late push never overwrites a newer read.
849
+
730
850
  ### Offline Database Settlement And Packaging
731
851
 
732
852
  The Node-only `@serve.zone/interfaces/runtime` export includes isolated V1
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '31.8.0',
6
+ version: '31.9.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -0,0 +1,45 @@
1
+ import { captureNetwork, networkCounter, networkId, networkRecord, requireNetwork } from '../private/networkcontract.js';
2
+ import { isOneOf, producerSafe } from '../private/producercontract.js';
3
+
4
+ const clusterRuntimePhases = ['swarm', 'draining', 'switching', 'pallet'] as const;
5
+
6
+ /**
7
+ * Which runtime owns a cluster's workloads.
8
+ *
9
+ * `swarm` is Coreflow's; `draining` stops every service while its volumes stay; `switching` lets the
10
+ * cluster's nodes join the runtime network while nothing runs; `pallet` is Pallet's alone.
11
+ */
12
+ export type TClusterRuntimePhase = typeof clusterRuntimePhases[number];
13
+
14
+ /**
15
+ * The runtime phase of one cluster, stored beside the cluster document rather than inside it: a
16
+ * cluster update merges caller data, and no caller may move a phase with it.
17
+ */
18
+ export interface IClusterRuntime {
19
+ /** The cluster id. */
20
+ id: string;
21
+ phase: TClusterRuntimePhase;
22
+ /** Compare-and-swap counter; every phase write advances it by one. */
23
+ generation: number;
24
+ /** Unix milliseconds on Cloudly's clock. */
25
+ changedAt: number;
26
+ /** The administrator or producer that made the change. */
27
+ changedBy: string;
28
+ }
29
+
30
+ /**
31
+ * Why a cluster cannot leave `swarm` yet. Each blocker names the node or service it is about, so an
32
+ * administrator can act on it without a second query.
33
+ */
34
+ export type TClusterRuntimeBlocker =
35
+ | { code: 'relay-not-enabled' | 'relay-not-registered' | 'address-plan-missing' | 'vpn-unconfigured' }
36
+ | { code: 'node-without-pallet-credential' | 'node-arch-unsupported'; nodeId: string }
37
+ | { code: 'service-multi-cluster-scope' | 'service-authority-unsupported' | 'platform-binding-present'; serviceId: string };
38
+
39
+ /** Structural snapshot for exact persistence; which phase may follow which is the cutover's decision. */
40
+ export const snapshotClusterRuntime = (value: unknown): IClusterRuntime => producerSafe(() => {
41
+ const item = networkRecord(captureNetwork(value), ['id', 'phase', 'generation', 'changedAt', 'changedBy']);
42
+ requireNetwork(isOneOf(clusterRuntimePhases, item.phase));
43
+ return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation),
44
+ changedAt: networkCounter(item.changedAt), changedBy: networkId(item.changedBy) };
45
+ });
package/ts/data/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './cloudlyconfig.js';
2
2
  export * from './cluster.js';
3
+ export * from './clusterruntime.js';
3
4
  export * from './clusteringress.js';
4
5
  export * from './config.js';
5
6
  export * from './corestore.js';
@@ -24,10 +25,12 @@ export * from './isolatedrestore.golden.js';
24
25
  export * from './mail.js';
25
26
  export * from './nodecredential.js';
26
27
  export * from './nodeenrollment.js';
28
+ export * from './networkaddressplan.js';
27
29
  export * from './networkauthority.js';
28
30
  export * from './networkcredential.js';
29
31
  export * from './networkdelivery.js';
30
32
  export * from './networklease.js';
33
+ export * from './networknode.js';
31
34
  export * from './networkprojection.js';
32
35
  export type { IRuntimeNetworkRouterPacketGrantV1, IRuntimeNetworkRouterProjectionV1 } from './networkrouter.js';
33
36
  export * from './networkprotection.js';
@@ -48,6 +51,7 @@ export * from './sparknodereport.js';
48
51
  export * from './service.js';
49
52
  export * from './servicecredential.js';
50
53
  export * from './serviceports.js';
54
+ export * from './serviceruntime.js';
51
55
  export * from './status.js';
52
56
  export * from './taskexecution.js';
53
57
  export * from './traffic.js';