@serve.zone/interfaces 32.43.0 → 32.45.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 (34) hide show
  1. package/changelog.md +65 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/index.d.ts +3 -0
  4. package/dist_ts/data/index.js +3 -1
  5. package/dist_ts/data/networkegressretirement.d.ts +2 -1
  6. package/dist_ts/data/networkegressretirement.js +1 -1
  7. package/dist_ts/data/networkprojection.d.ts +10 -0
  8. package/dist_ts/data/networkprojection.js +15 -2
  9. package/dist_ts/data/networkprojectionretirement.d.ts +52 -0
  10. package/dist_ts/data/networkprojectionretirement.js +33 -0
  11. package/dist_ts/data/networkprotection.d.ts +29 -1
  12. package/dist_ts/data/networkprotection.js +45 -1
  13. package/dist_ts/data/noderetirement.d.ts +3 -1
  14. package/dist_ts/data/noderetirement.js +1 -1
  15. package/dist_ts/data/runtimeassignment.d.ts +24 -3
  16. package/dist_ts/data/runtimeassignment.js +19 -3
  17. package/dist_ts/data/serviceplatformownership.d.ts +55 -0
  18. package/dist_ts/data/serviceplatformownership.js +62 -0
  19. package/dist_ts/data/serviceruntime.d.ts +49 -1
  20. package/dist_ts/data/serviceruntime.js +44 -2
  21. package/dist_ts/requests/network.d.ts +76 -1
  22. package/package.json +5 -5
  23. package/readme.md +98 -4
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/data/index.ts +3 -0
  26. package/ts/data/networkegressretirement.ts +2 -1
  27. package/ts/data/networkprojection.ts +20 -1
  28. package/ts/data/networkprojectionretirement.ts +72 -0
  29. package/ts/data/networkprotection.ts +52 -1
  30. package/ts/data/noderetirement.ts +3 -1
  31. package/ts/data/runtimeassignment.ts +33 -5
  32. package/ts/data/serviceplatformownership.ts +95 -0
  33. package/ts/data/serviceruntime.ts +68 -1
  34. package/ts/requests/network.ts +65 -1
@@ -73,7 +73,7 @@ export declare const serviceRuntimeContract: Readonly<{
73
73
  readonly maximumAssignmentPageSize: 256;
74
74
  }>;
75
75
  declare const serviceRuntimeWaitingReasons: readonly ["node-network-pending", "conflict", "failure-backoff", "storage-import-pending", "bindings-pending", "legacy-adoption-pending", "objectstorage-retention-pending"];
76
- declare const serviceRuntimeRefusals: readonly ["cluster-runtime-missing", "organization-missing", "image-plan-missing", "platform-unsupported", "secrets-unsupported", "volumes-unsupported", "port-protocol-unsupported", "ports-too-many", "readiness-target-undeclared", "environment-too-large", "no-eligible-node", "replica-limit", "node-authority-unsupported", "network-policy-missing", "network-policy-mismatch", "network-workload-pool-missing", "network-workload-pool-exhausted", "volume-driver-unsupported", "volume-options-unsupported", "volume-shared-unsupported", "volume-placement-unsupported", "volume-initialization-unknown", "volume-ownership-unknown", "objectstorage-endpoint-missing", "objectstorage-binding-invalid", "objectstorage-environment-conflict", "objectstorage-endpoint-unselected", "database-endpoint-missing", "database-endpoint-unselected", "published-port-reserved", "published-port-symmetric-without-egress"];
76
+ declare const serviceRuntimeRefusals: readonly ["cluster-runtime-missing", "organization-missing", "image-plan-missing", "platform-unsupported", "secrets-unsupported", "volumes-unsupported", "port-protocol-unsupported", "ports-too-many", "readiness-target-undeclared", "environment-too-large", "no-eligible-node", "replica-limit", "node-authority-unsupported", "network-policy-missing", "network-policy-mismatch", "network-workload-pool-missing", "network-workload-pool-exhausted", "volume-driver-unsupported", "volume-options-unsupported", "volume-shared-unsupported", "volume-placement-unsupported", "volume-initialization-unknown", "volume-ownership-unknown", "objectstorage-endpoint-missing", "objectstorage-binding-invalid", "objectstorage-environment-conflict", "objectstorage-endpoint-unselected", "database-endpoint-missing", "database-endpoint-unselected", "published-port-reserved", "published-port-symmetric-without-egress", "readiness-network-unsupported"];
77
77
  /**
78
78
  * Why the producer is not converging yet, though nothing needs an administrator:
79
79
  * `node-network-pending` waits for a node's network readiness, `conflict` for a sweep over inputs
@@ -137,6 +137,13 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
137
137
  * which the handoff leases' `runtimeNetworkHandoffSnatPortRange` owns; a document written before
138
138
  * that rule can hold one. `published-port-symmetric-without-egress` is a `symmetric` entry in a
139
139
  * document whose `publicEgress` is false, which a projection endpoint refuses.
140
+ *
141
+ * `readiness-network-unsupported` is a spec whose network is `isolated` and whose readiness probe is
142
+ * `tcp` or `http`: an isolated workload has no address its node can dial, so the probe could never
143
+ * pass and the node refuses the run (`TRuntimeAssignmentFailureCause` `refused` for
144
+ * `unsupported-readiness`). `isServiceRuntimeReadinessReachable` is this rule; a producer answers
145
+ * what it returns. The spec snapshot still reads such a spec, so a stored one stays readable and
146
+ * is answered with this refusal rather than refused as data.
140
147
  */
141
148
  export type TServiceRuntimeRefusal = typeof serviceRuntimeRefusals[number];
142
149
  interface IServiceRuntimeStatusBase {
@@ -164,6 +171,32 @@ export type IServiceRuntimeStatus = IServiceRuntimeStatusBase & ({
164
171
  * node admitted but has not yet reported on reads as undelivered.
165
172
  */
166
173
  export type TServiceRuntimeAssignmentDelivery = 'undelivered' | 'delivered';
174
+ /**
175
+ * Why one dispatch of an assignment revision to its node ended as it did.
176
+ *
177
+ * The node holds the revision: `accepted`, `replay` or `historical` is what the node answered to
178
+ * the push, and `acknowledged` a revision the node's current session acknowledged before, answered
179
+ * from that acknowledgement without pushing it again.
180
+ *
181
+ * The node does not hold it from this dispatch: `node-busy` while the node already carries its limit
182
+ * of pushes in flight, `node-not-connected` while no live peer holds a session for the node,
183
+ * `session-moved` when the session the dispatch began on is no longer the node's current one,
184
+ * `intent-not-current` when the revision is no longer the one the controller wants on that node,
185
+ * `request-unbound` when the request does not bind to the session it would travel on,
186
+ * `node-refused` when the node (or the relay carrying it) answered the push with a refusal,
187
+ * `push-failed` when the push got no answer in time or its carrier failed, and
188
+ * `acknowledgement-unbound` when the answer names another revision.
189
+ */
190
+ export declare const assignmentDispatchReasons: readonly ["accepted", "replay", "historical", "acknowledged", "node-busy", "node-not-connected", "session-moved", "intent-not-current", "request-unbound", "node-refused", "push-failed", "acknowledgement-unbound"];
191
+ export type TAssignmentDispatchReason = typeof assignmentDispatchReasons[number];
192
+ /** The last dispatch of a slot's current assignment, as the controller recorded it. Value-free. */
193
+ export interface IServiceRuntimeAssignmentDispatch {
194
+ /** Whether the node holds the revision after this dispatch. */
195
+ delivered: boolean;
196
+ reason: TAssignmentDispatchReason;
197
+ /** Unix milliseconds on the controller's clock when this dispatch ended. */
198
+ at: number;
199
+ }
167
200
  /** One replica slot's current assignment, as an administrator reads it. */
168
201
  export interface IServiceRuntimeAssignmentView {
169
202
  serviceId: string;
@@ -180,12 +213,27 @@ export interface IServiceRuntimeAssignmentView {
180
213
  /** Joined stop and remove receipts of this attempt. */
181
214
  terminalReceiptCount: 0 | 1 | 2;
182
215
  delivery: TServiceRuntimeAssignmentDelivery;
216
+ /**
217
+ * The last dispatch of the current revision while the controller still remembers one, null when
218
+ * it remembers none. Absent from a controller that does not report dispatches. Dispatch is the
219
+ * controller's push; `delivery` stays the node's own evidence.
220
+ */
221
+ dispatch?: IServiceRuntimeAssignmentDispatch | null;
183
222
  }
184
223
  /**
185
224
  * Structural snapshot for exact persistence and for a spec write. Whether the cluster, the nodes and
186
225
  * the platform endpoints exist, and whether the service declares the readiness target, is Cloudly's.
187
226
  */
188
227
  export declare const snapshotServiceRuntimeSpec: (value: unknown) => IServiceRuntimeSpec;
228
+ /**
229
+ * Whether a spec's readiness probe can reach its workload: false exactly for an `isolated` network
230
+ * with a `tcp` or `http` probe, which the producer answers as `readiness-network-unsupported`. The
231
+ * spec is read through `snapshotServiceRuntimeSpec`, so a value that is no spec is refused with its
232
+ * rejection rather than judged.
233
+ */
234
+ export declare const isServiceRuntimeReadinessReachable: (value: unknown) => boolean;
235
+ /** Exactly a recorded dispatch of an assignment: a known reason and a positive Unix millisecond time. */
236
+ export declare const isServiceRuntimeAssignmentDispatch: (value: unknown) => value is IServiceRuntimeAssignmentDispatch;
189
237
  /** Structural snapshot for exact persistence: a reason belongs to exactly one state. */
190
238
  export declare const snapshotServiceRuntimeStatus: (value: unknown) => IServiceRuntimeStatus;
191
239
  export {};
@@ -28,7 +28,28 @@ const serviceRuntimeRefusals = [
28
28
  'volume-placement-unsupported', 'volume-initialization-unknown', 'volume-ownership-unknown',
29
29
  'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
30
30
  'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
31
- 'published-port-reserved', 'published-port-symmetric-without-egress',
31
+ 'published-port-reserved', 'published-port-symmetric-without-egress', 'readiness-network-unsupported',
32
+ ];
33
+ /**
34
+ * Why one dispatch of an assignment revision to its node ended as it did.
35
+ *
36
+ * The node holds the revision: `accepted`, `replay` or `historical` is what the node answered to
37
+ * the push, and `acknowledged` a revision the node's current session acknowledged before, answered
38
+ * from that acknowledgement without pushing it again.
39
+ *
40
+ * The node does not hold it from this dispatch: `node-busy` while the node already carries its limit
41
+ * of pushes in flight, `node-not-connected` while no live peer holds a session for the node,
42
+ * `session-moved` when the session the dispatch began on is no longer the node's current one,
43
+ * `intent-not-current` when the revision is no longer the one the controller wants on that node,
44
+ * `request-unbound` when the request does not bind to the session it would travel on,
45
+ * `node-refused` when the node (or the relay carrying it) answered the push with a refusal,
46
+ * `push-failed` when the push got no answer in time or its carrier failed, and
47
+ * `acknowledgement-unbound` when the answer names another revision.
48
+ */
49
+ export const assignmentDispatchReasons = [
50
+ 'accepted', 'replay', 'historical', 'acknowledged',
51
+ 'node-busy', 'node-not-connected', 'session-moved', 'intent-not-current', 'request-unbound', 'node-refused',
52
+ 'push-failed', 'acknowledgement-unbound',
32
53
  ];
33
54
  /** Sorted, duplicate-free and read by the vocabulary the ids belong to: a node's, or the canonical one. */
34
55
  const sortedIds = (value, maximum, read) => networkSorted(networkList(value, maximum, read), id => id);
@@ -86,6 +107,27 @@ export const snapshotServiceRuntimeSpec = (value) => producerSafe(() => {
86
107
  placement: placementOf(item.placement), replicasPerNode: item.replicasPerNode, hold: item.hold,
87
108
  profile: profileOf(item.profile), network: networkOf(item.network), revision: networkCounter(item.revision) };
88
109
  });
110
+ /**
111
+ * Whether a spec's readiness probe can reach its workload: false exactly for an `isolated` network
112
+ * with a `tcp` or `http` probe, which the producer answers as `readiness-network-unsupported`. The
113
+ * spec is read through `snapshotServiceRuntimeSpec`, so a value that is no spec is refused with its
114
+ * rejection rather than judged.
115
+ */
116
+ export const isServiceRuntimeReadinessReachable = (value) => {
117
+ const { profile, network } = snapshotServiceRuntimeSpec(value);
118
+ return network.mode === 'attached' || profile.readiness.kind === 'process';
119
+ };
120
+ /** Exactly a recorded dispatch of an assignment: a known reason and a positive Unix millisecond time. */
121
+ export const isServiceRuntimeAssignmentDispatch = (value) => {
122
+ try {
123
+ const item = networkRecord(captureNetwork(value), ['delivered', 'reason', 'at']);
124
+ networkCounter(item.at);
125
+ return typeof item.delivered === 'boolean' && isOneOf(assignmentDispatchReasons, item.reason);
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ };
89
131
  /** Structural snapshot for exact persistence: a reason belongs to exactly one state. */
90
132
  export const snapshotServiceRuntimeStatus = (value) => producerSafe(() => {
91
133
  const item = networkRecord(captureNetwork(value), ['id', 'specRevision', 'state', 'reason', 'changedAt']);
@@ -102,4 +144,4 @@ export const snapshotServiceRuntimeStatus = (value) => producerSafe(() => {
102
144
  requireNetwork(item.state === 'refused' && isOneOf(serviceRuntimeRefusals, item.reason));
103
145
  return { ...base, state: item.state, reason: item.reason };
104
146
  });
105
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZXJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3NlcnZpY2VydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxhQUFhLEVBQzVILGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsdUJBQXVCLEVBQUUsa0JBQWtCLEVBQ2hILG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUF5RDVELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDbEQsc0JBQXNCLEVBQUUsRUFBRTtJQUMxQiwrRkFBK0Y7SUFDL0YsZUFBZSxFQUFFLEVBQUU7SUFDbkIsb0hBQW9IO0lBQ3BILGtCQUFrQixFQUFFLCtCQUErQixDQUFDLHdCQUF3QjtJQUM1RSx3QkFBd0IsRUFBRSwrQkFBK0IsQ0FBQyx3QkFBd0I7SUFDbEYsMEZBQTBGO0lBQzFGLG1CQUFtQixFQUFFLEdBQUc7SUFDeEIsZ0dBQWdHO0lBQ2hHLHlCQUF5QixFQUFFLEdBQUc7Q0FDdEIsQ0FBQyxDQUFDO0FBRVosTUFBTSw0QkFBNEIsR0FBRztJQUNuQyxzQkFBc0IsRUFBRSxVQUFVLEVBQUUsaUJBQWlCLEVBQUUsd0JBQXdCLEVBQUUsa0JBQWtCO0lBQ25HLHlCQUF5QixFQUFFLGlDQUFpQztDQUNwRCxDQUFDO0FBQ1gsTUFBTSxzQkFBc0IsR0FBRztJ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
147
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZXJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3NlcnZpY2VydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxhQUFhLEVBQzVILGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsdUJBQXVCLEVBQUUsa0JBQWtCLEVBQ2hILG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUF5RDVELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDbEQsc0JBQXNCLEVBQUUsRUFBRTtJQUMxQiwrRkFBK0Y7SUFDL0YsZUFBZSxFQUFFLEVBQUU7SUFDbkIsb0hBQW9IO0lBQ3BILGtCQUFrQixFQUFFLCtCQUErQixDQUFDLHdCQUF3QjtJQUM1RSx3QkFBd0IsRUFBRSwrQkFBK0IsQ0FBQyx3QkFBd0I7SUFDbEYsMEZBQTBGO0lBQzFGLG1CQUFtQixFQUFFLEdBQUc7SUFDeEIsZ0dBQWdHO0lBQ2hHLHlCQUF5QixFQUFFLEdBQUc7Q0FDdEIsQ0FBQyxDQUFDO0FBRVosTUFBTSw0QkFBNEIsR0FBRztJQUNuQyxzQkFBc0IsRUFBRSxVQUFVLEVBQUUsaUJBQWlCLEVBQUUsd0JBQXdCLEVBQUUsa0JBQWtCO0lBQ25HLHlCQUF5QixFQUFFLGlDQUFpQztDQUNwRCxDQUFDO0FBQ1gsTUFBTSxzQkFBc0IsR0FBRztJ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
@@ -4,6 +4,7 @@ import type { IRuntimeNetworkAddressPlan, TRuntimeNetworkAddressPlanWritableData
4
4
  import type { IRuntimeNetworkNodeReadiness, IRuntimeRouterIncarnation } from '../data/networknode.js';
5
5
  import type { IPrivateNetwork, IServicePrivateNetworks, ISetServicePrivateNetworks, TPrivateNetworkMutation } from '../data/privatenetwork.js';
6
6
  import type { IServiceNetworkEgress, IServicePublishedPorts, ISetServiceNetworkEgress, ISetServicePublishedPorts } from '../data/networkprojection.js';
7
+ import type { IServicePlatformOwnership } from '../data/serviceplatformownership.js';
7
8
  /** Current organization picker data. Entries are never cached authorization grants. */
8
9
  export interface IReq_Cloudly_ListOwnedOrganizations extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_ListOwnedOrganizations> {
9
10
  method: 'listOwnedOrganizations';
@@ -97,7 +98,14 @@ export interface IReq_Cloudly_SetServicePrivateNetworks extends plugins.typedreq
97
98
  replayed: boolean;
98
99
  };
99
100
  }
100
- /** null means the service has never had a policy document, neither published ports nor egress. */
101
+ /**
102
+ * null means the service has never had a policy document, neither published ports nor egress.
103
+ *
104
+ * The authority follows the service (`IServicePlatformOwnership`): a platform-owned service is read
105
+ * by a verified administrator, every other service by a federated sign-in holding the named
106
+ * organization's freshly introspected `owner` role. A caller without that authority, or naming no
107
+ * such service, is answered one denial text, whichever it is.
108
+ */
101
109
  export interface IReq_Cloudly_GetServicePublishedPorts extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetServicePublishedPorts> {
102
110
  method: 'getServicePublishedPorts';
103
111
  request: {
@@ -119,6 +127,15 @@ export interface IReq_Cloudly_GetServicePublishedPorts extends plugins.typedrequ
119
127
  * attached spec stating `publicEgress: false` and `platformEndpointIds: []` matches it and reaches
120
128
  * no public address and no platform endpoint; `network-policy-missing` means the service has no
121
129
  * document at all.
130
+ *
131
+ * The authority follows the service (`IServicePlatformOwnership`): a platform-owned service is
132
+ * written by a verified administrator, every other service by a federated sign-in holding the named
133
+ * organization's freshly introspected `owner` role, and an administrator does not substitute for
134
+ * that owner. A caller without that authority, or naming no such service, is answered one denial
135
+ * text, whichever it is. A designation that changes while the write is staged refuses it as the
136
+ * policy write's own `conflict`, the refusal a stale `expectedRevision` is also answered (read the
137
+ * document and the authority again); it is a service network policy refusal, not one of
138
+ * `servicePlatformOwnershipRefusals`.
122
139
  */
123
140
  export interface IReq_Cloudly_SetServicePublishedPorts extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_SetServicePublishedPorts> {
124
141
  method: 'setServicePublishedPorts';
@@ -183,6 +200,64 @@ export interface IReq_Admin_Cloudly_SetServiceNetworkEgress extends plugins.type
183
200
  replayed: boolean;
184
201
  };
185
202
  }
203
+ /**
204
+ * Designates one service platform-owned (`IServicePlatformOwnership`), so that a verified
205
+ * administrator, and no organization owner, holds the authority over its published ports.
206
+ *
207
+ * Only a verified administrator calls it. Cloudly designates only a service of a platform
208
+ * organization (`isPlatformOrganizationId`: a canonical identifier carrying `:`, such as
209
+ * `legacy:Service:<id>` or `Organization:<id>`) and refuses every other organization id, an
210
+ * idp.global one or one that is no canonical identifier, as `organization-not-platform` before it
211
+ * reads the service. It refuses a service that is not the named organization's (`service-unknown`)
212
+ * and a deleting one (`service-deleting`); refusals are `servicePlatformOwnershipRefusals`. A
213
+ * service already designated answers its stored designation unchanged with `changed: false`.
214
+ */
215
+ export interface IReq_Admin_Cloudly_DesignatePlatformOwnedService extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_DesignatePlatformOwnedService> {
216
+ method: 'designatePlatformOwnedService';
217
+ request: {
218
+ identity: IIdentityCredential;
219
+ organizationId: string;
220
+ serviceId: string;
221
+ };
222
+ response: {
223
+ platformOwnership: IServicePlatformOwnership;
224
+ changed: boolean;
225
+ };
226
+ }
227
+ /**
228
+ * Withdraws a service's platform-owned designation; its published ports stay as they are, and from
229
+ * then on the organization-owner rule governs them. A designated service is always a platform
230
+ * organization's, which has no idp.global owner, so after the withdrawal nobody writes or reads its
231
+ * published ports until an administrator designates it again: the authority fails closed.
232
+ *
233
+ * Only a verified administrator calls it. Refusals are `service-unknown` and `conflict`; a service
234
+ * without a designation answers `changed: false`.
235
+ */
236
+ export interface IReq_Admin_Cloudly_UndesignatePlatformOwnedService extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_UndesignatePlatformOwnedService> {
237
+ method: 'undesignatePlatformOwnedService';
238
+ request: {
239
+ identity: IIdentityCredential;
240
+ organizationId: string;
241
+ serviceId: string;
242
+ };
243
+ response: {
244
+ platformOwnership: null;
245
+ changed: boolean;
246
+ };
247
+ }
248
+ /** The service's platform-owned designation, or null when it has none. Only a verified administrator reads it. */
249
+ export interface IReq_Admin_Cloudly_GetServicePlatformOwnership extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_GetServicePlatformOwnership> {
250
+ method: 'getServicePlatformOwnership';
251
+ /** Refused `service-unknown` when no service of that id belongs to the named organization. */
252
+ request: {
253
+ identity: IIdentityCredential;
254
+ organizationId: string;
255
+ serviceId: string;
256
+ };
257
+ response: {
258
+ platformOwnership: IServicePlatformOwnership | null;
259
+ };
260
+ }
186
261
  export interface IRequest_Any_Cloudly_GetNetworkNodes {
187
262
  method: 'getNetworkNodes';
188
263
  request: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.43.0",
3
+ "version": "32.45.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -27,11 +27,11 @@
27
27
  "@tsclass/tsclass": "^9.5.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@git.zone/tsbuild": "^5.1.1",
31
- "@git.zone/tsdoc": "^3.0.1",
30
+ "@git.zone/tsbuild": "^5.1.2",
31
+ "@git.zone/tsdoc": "^3.0.2",
32
32
  "@git.zone/tsrun": "^3.0.2",
33
- "@git.zone/tstest": "6.3.4",
34
- "@types/node": "26.6.2"
33
+ "@git.zone/tstest": "^7.1.0",
34
+ "@types/node": "26.6.3"
35
35
  },
36
36
  "files": [
37
37
  "ts/**/*",
package/readme.md CHANGED
@@ -409,7 +409,17 @@ authorities and their digests are validated, and either that does not validate i
409
409
  protection journal separately from projection admission. It binds a complete
410
410
  protected-authority reference, node/controller scope, authenticated reporter,
411
411
  boot identity and exact native journal reference to a monotonic receipt chain.
412
- A null `nativeBarrier` explicitly removes allocation eligibility. A boot or
412
+ A null `nativeBarrier` explicitly removes allocation eligibility. A node sends one
413
+ only in answer to the `retirement` request of a signed projection (see
414
+ [Projection Retirement Requests](#projection-retirement-requests)): its egress
415
+ ownership is being retired with the `withdrawn` disposition, and it mints no
416
+ positive receipt for the chain afterwards
417
+ (`runtimeNetworkProtectionContract.withdrawal`). Nothing else withdraws. The
418
+ node's allocation-pool guard is retained in the kernel when the node process
419
+ ends, and its boot unit restores it before networking starts, so a clean stop, a
420
+ crash, a reboot or being offline leaves the last receipt current. A reconnect
421
+ binds a new reporter session, which the old receipt no longer matches, and the
422
+ node's first positive receipt under that session restores eligibility. A boot or
413
423
  protection change requires new native journal evidence. These portable values
414
424
  do not inspect a kernel or authenticate their own producer.
415
425
 
@@ -505,6 +515,34 @@ list is a valid policy meaning the service publishes nothing. Callers never supp
505
515
  from the accepted policy revision and digest, and node-level host-port placement
506
516
  stays Cloudly's decision.
507
517
 
518
+ Who authors a service's published ports follows the service. A service of an
519
+ idp.global organization is that organization's: only a federated sign-in holding
520
+ its freshly introspected `owner` role reads and writes them, and an
521
+ infrastructure administrator does not substitute for it. A platform-owned
522
+ service (`data.IServicePlatformOwnership`: `organizationId`, `serviceId`, the
523
+ designating administrator's `actorId`, `designatedAt`) belongs to no idp.global
524
+ organization, such as the cluster ingress or a platform call router; only a
525
+ verified administrator reads and writes its published ports, as only an
526
+ administrator writes its egress. An administrator designates such a service
527
+ with `designatePlatformOwnedService`, withdraws it with
528
+ `undesignatePlatformOwnedService` and reads it with `getServicePlatformOwnership`
529
+ (`requests.network.IReq_Admin_Cloudly_DesignatePlatformOwnedService`,
530
+ `IReq_Admin_Cloudly_UndesignatePlatformOwnedService`,
531
+ `IReq_Admin_Cloudly_GetServicePlatformOwnership`). Only a platform
532
+ organization's service is designated: `data.isPlatformOrganizationId` is true
533
+ exactly for a canonical identifier that carries `:` (`legacy:Service:<id>`,
534
+ `Organization:<id>`), and false for an idp.global nanoid, whichever character it
535
+ starts with, and for any value that is not a canonical identifier, such as an
536
+ empty or whitespace id. Cloudly refuses every other id as
537
+ `organization-not-platform`, and further designation refusals are
538
+ `service-unknown`, `service-deleting` and `conflict`
539
+ (`data.servicePlatformOwnershipRefusals`). The designation grants no port, egress
540
+ or placement by itself. Withdrawing it leaves the service's published ports as
541
+ they are with no writer, because a platform organization has no idp.global
542
+ owner, until an administrator designates the service again. A published-port
543
+ write staged while the designation changes is refused as the policy write's
544
+ `conflict`.
545
+
508
546
  A published entry with `hostPortEnd` publishes every port from `hostPort` to
509
547
  `hostPortEnd` inclusive, each to the same port inside the workload, so its
510
548
  `targetPort` equals `hostPort` and a one-port range is written without the member.
@@ -682,6 +720,59 @@ reaches only the endpoints it selects. The platform endpoint contract itself
682
720
  (`{ id, address, protocol, port }`) names no node, which is why this statement is
683
721
  per projection.
684
722
 
723
+ #### Projection Retirement Requests
724
+
725
+ The optional `IRuntimeNetworkProjection.retirement`
726
+ (`IRuntimeNetworkProjectionRetirement { disposition: 'withdrawn', protectedAuthority }`)
727
+ is Cloudly's request that a running node withdraw its allocation protection,
728
+ because its egress ownership is being retired with the `withdrawn` disposition.
729
+ It travels in the projection because that is the one authenticated, monotonic
730
+ channel the node already admits, journals and reads on every pass, and whose
731
+ acknowledged head Cloudly already tracks: the controller's signature covers it, so
732
+ a node cannot invent it; the projection chain is bound to the controller epoch,
733
+ node and runtime namespace, so it cannot be carried elsewhere; and a node's
734
+ acknowledged head proves the node admitted it. A retired node never returns
735
+ (`runtimeNetworkEgressRetirementContract.reentry`), so no later chain of the same
736
+ node can meet a stale request.
737
+
738
+ `protectedAuthority` is the authority current when Cloudly first signed the
739
+ request; its `id` names the receipt chain's authority chain.
740
+ `validateRuntimeNetworkProjection` accepts it only on a `cloudly` projection and
741
+ only naming the projection's own authority chain at a generation the projection
742
+ has reached (the current authority itself at the same generation).
743
+ `admitRuntimeNetworkProjection` latches it: it may first appear on any projection,
744
+ including the first, and that first appearance names exactly the projection's
745
+ current `protectedAuthority` (an older generation is refused, because the node's
746
+ positive receipt under the current authority would then be newer than the request
747
+ and the withdrawal could never complete). Afterwards every successor carries it
748
+ unchanged, never dropped and never rewritten, so it stays fixed while the
749
+ authority chain moves on. Omitted, it keeps the canonical bytes and asks for
750
+ nothing; it grants and withdraws no packet flow. Consumers on interfaces earlier
751
+ than 32.44.0 read projections with exact keys and refuse one that carries
752
+ `retirement`, so Cloudly sends the request only to nodes that report a compatible
753
+ interfaces version.
754
+ `snapshotRuntimeNetworkProjectionRetirement` is the exact-key reader and
755
+ `runtimeNetworkProjectionRetirementContract` states the rules.
756
+
757
+ The node answers with the null-barrier successor of its protection receipt chain
758
+ and mints no positive receipt for that chain again. Its allocation-pool guard
759
+ stays enforced: the answer withdraws eligibility, not protection.
760
+
761
+ `bindRuntimeNetworkProtectionReceiptRetirement(receipt, previous, retirement)` is
762
+ the receiver's gate for every report from a node whose projections carry the
763
+ request, beside `admitRuntimeNetworkProtectionReceipt` with the same `previous`
764
+ (the receiver's current receipt for the node, or null). The receipt must belong to
765
+ the retiring authority chain and `previous`, when given, must be the same chain's
766
+ exact predecessor or the receipt itself. The null answer always passes. A positive
767
+ receipt passes only while it can predate the node's admission of the request, so a
768
+ receipt the node minted before it saw the request still drains from its outbox
769
+ and the withdrawal behind it is never wedged: it names no authority newer than the
770
+ request's (a newer one could only come from a projection already carrying the
771
+ request), and its predecessor is not a null withdrawal (once the receiver holds
772
+ the answer, no positive receipt follows). A positive receipt minted after the
773
+ request against the same authority cannot be told apart from one minted before;
774
+ the node's obligation is never to mint one.
775
+
685
776
  `admitRuntimeNetworkProjection` checks exact predecessor/replay identity and
686
777
  rejects changed same-generation content, regressed membership/readiness evidence,
687
778
  changed immutable address/DNS keys, and omitted packet grants without explicit
@@ -803,7 +894,8 @@ exact protected authority the owner is about to leave, once all five hold:
803
894
  its own fence. Every other remaining owner has exactly one entry.
804
895
 
805
896
  `lastReceipt` is the node's last accepted protection receipt. A `withdrawn` node
806
- took part in its own retirement, so it always has one (its null withdrawal); a
897
+ took part in its own retirement, so it always has one (its null withdrawal, the
898
+ answer to its projection's `retirement` request); a
807
899
  `lost` node may have none. Neither disposition shortens the fence: a lost node
808
900
  leaves behind exactly the same controller-owned evidence as a withdrawn one.
809
901
  `snapshotRuntimeNetworkEgressRetirement` is the exact-key reader and
@@ -1927,8 +2019,10 @@ document. `snapshotNodeRuntimeRetirement` checks the record.
1927
2019
 
1928
2020
  `TNodeRuntimeRetirementDisposition` (`nodeRuntimeRetirementDispositions`,
1929
2021
  `snapshotNodeRuntimeRetirementDisposition`) says how the node leaves. `withdrawn`: the node still
1930
- runs and takes part — its slots are removed against its own removal receipts and its Pallet
1931
- withdraws its protection receipt before its credentials are revoked. `lost`: an administrator
2022
+ runs and takes part — its slots are removed against its own removal receipts, Cloudly asks it
2023
+ through its projection's `retirement` request (see
2024
+ [Projection Retirement Requests](#projection-retirement-requests)), and its Pallet answers by
2025
+ withdrawing its protection receipt before its credentials are revoked. `lost`: an administrator
1932
2026
  states the node will never report again, which authorizes abandoning its slots without removal
1933
2027
  receipts and nothing else; its egress fence is the same as a withdrawn node's.
1934
2028
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.43.0',
6
+ version: '32.45.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
package/ts/data/index.ts CHANGED
@@ -49,6 +49,8 @@ export { runtimeNetworkHostContract } from './networkhost.js';
49
49
  export type { IRuntimeNetworkWorkloadIngress, IRuntimeNetworkWorkloadIngressPacketGrant,
50
50
  IRuntimeNetworkWorkloadIngressProjection } from './networkworkloadingress.js';
51
51
  export { runtimeNetworkWorkloadIngressContract } from './networkworkloadingress.js';
52
+ export type { IRuntimeNetworkProjectionRetirement } from './networkprojectionretirement.js';
53
+ export { runtimeNetworkProjectionRetirementContract, snapshotRuntimeNetworkProjectionRetirement } from './networkprojectionretirement.js';
52
54
  export * from './networkprotection.js';
53
55
  export * from './networksigning.js';
54
56
  export * from './privatenetwork.js';
@@ -74,6 +76,7 @@ export * from './sparklegacyretirement.js';
74
76
  export * from './sparknodereport.js';
75
77
  export * from './service.js';
76
78
  export * from './servicecredential.js';
79
+ export * from './serviceplatformownership.js';
77
80
  export * from './serviceports.js';
78
81
  export * from './serviceruntime.js';
79
82
  export * from './servicevolumeinitialization.js';
@@ -44,7 +44,8 @@ export interface IRuntimeNetworkEgressRetirementClearance extends IRuntimeNetwor
44
44
  * so it can never clear another owner's endpoints, and it leaves behind its own fence.
45
45
  *
46
46
  * `lastReceipt` is the node's last accepted protection receipt. A `withdrawn` node took part in its
47
- * retirement, so it always has one — its null withdrawal; a `lost` node may never have reported.
47
+ * retirement, so it always has one — its null withdrawal, the answer to its projection's
48
+ * `retirement` request; a `lost` node may never have reported.
48
49
  *
49
50
  * A structural snapshot and digest do not authenticate any of it: the controller must fence the
50
51
  * rows it read together with the authority it stages.
@@ -11,6 +11,8 @@ import { hostPacketGrantsOf, snapshotHostProjection,
11
11
  type IRuntimeNetworkHostPacketGrant, type IRuntimeNetworkHostProjection } from './networkhost.js';
12
12
  import { snapshotWorkloadIngressProjection, workloadIngressPacketGrantsOf,
13
13
  type IRuntimeNetworkWorkloadIngressPacketGrant, type IRuntimeNetworkWorkloadIngressProjection } from './networkworkloadingress.js';
14
+ import { requireFirstProjectionRetirement, requireProjectionRetirement, snapshotRuntimeNetworkProjectionRetirement,
15
+ type IRuntimeNetworkProjectionRetirement } from './networkprojectionretirement.js';
14
16
  import { buildPrivateNetworkFqdn, composeServicePrivateNetworkDnsPolicy,
15
17
  snapshotPrivateNetwork, snapshotServicePrivateNetworks } from './privatenetwork.js';
16
18
  import { captureNetwork, failNetwork, networkCanonical, networkContains, networkCounter, networkDigest,
@@ -299,6 +301,15 @@ export interface IRuntimeNetworkProjection extends IRuntimeNetworkNodeScope {
299
301
  * It grants nothing: a workload still reaches only the endpoints it selects.
300
302
  */
301
303
  hostPlatformEndpointIds?: string[];
304
+ /**
305
+ * Present once Cloudly retires this node's egress ownership with the `withdrawn` disposition:
306
+ * the node withdraws its allocation protection with the null-barrier successor of its receipt
307
+ * chain and mints no positive receipt for it again (`IRuntimeNetworkProjectionRetirement`). Only
308
+ * a `cloudly` projection may carry it; it first appears naming exactly the projection's current
309
+ * `protectedAuthority`, and every successor carries it unchanged. Omitted, it keeps the canonical
310
+ * bytes and asks for nothing.
311
+ */
312
+ retirement?: IRuntimeNetworkProjectionRetirement;
302
313
  digest: TSha256Digest;
303
314
  }
304
315
 
@@ -566,10 +577,11 @@ export const snapshotRuntimeNetworkProjection = (value: unknown): IRuntimeNetwor
566
577
  const hasWorkloadIngress = captured !== null && typeof captured === 'object' && Object.hasOwn(captured, 'workloadIngress');
567
578
  const hasHostPlatformEndpoints = captured !== null && typeof captured === 'object'
568
579
  && Object.hasOwn(captured, 'hostPlatformEndpointIds');
580
+ const hasRetirement = captured !== null && typeof captured === 'object' && Object.hasOwn(captured, 'retirement');
569
581
  const item = networkRecord(captured, ['id', 'generation', 'previous', 'controller', 'nodeId', 'runtimeNamespace',
570
582
  'protectedAuthority', 'allocationAuthorities', 'handoff', 'networks', 'memberships', 'endpoints', 'dns', 'withdrawals', 'retiredLeases', 'retiredHandoffs', 'digest',
571
583
  ...(hasRouter ? ['router'] : []), ...(hasHost ? ['host'] : []), ...(hasWorkloadIngress ? ['workloadIngress'] : []),
572
- ...(hasHostPlatformEndpoints ? ['hostPlatformEndpointIds'] : [])]);
584
+ ...(hasHostPlatformEndpoints ? ['hostPlatformEndpointIds'] : []), ...(hasRetirement ? ['retirement'] : [])]);
573
585
  const hostPlatformEndpointIds = hasHostPlatformEndpoints
574
586
  ? networkSorted(networkList(item.hostPlatformEndpointIds, 64, networkId), value => value) : [];
575
587
  requireNetwork(!hasHostPlatformEndpoints || hostPlatformEndpointIds.length > 0);
@@ -585,6 +597,7 @@ export const snapshotRuntimeNetworkProjection = (value: unknown): IRuntimeNetwor
585
597
  ...(hasHost ? { host: snapshotHostProjection(item.host) } : {}),
586
598
  ...(hasWorkloadIngress ? { workloadIngress: snapshotWorkloadIngressProjection(item.workloadIngress) } : {}),
587
599
  ...(hasHostPlatformEndpoints ? { hostPlatformEndpointIds } : {}),
600
+ ...(hasRetirement ? { retirement: snapshotRuntimeNetworkProjectionRetirement(item.retirement) } : {}),
588
601
  retiredLeases: sortedReferences(item.retiredLeases), retiredHandoffs: sortedReferences(item.retiredHandoffs), digest: networkDigest(item.digest) };
589
602
  });
590
603
 
@@ -690,6 +703,7 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
690
703
  requireNetwork(previous.node !== current.node || previous.last < current.first);
691
704
  }
692
705
  for (const id of projection.hostPlatformEndpointIds ?? []) requireNetwork(current.platformEndpoints.some(platform => platform.id === id));
706
+ if (projection.retirement) requireProjectionRetirement(projection.retirement, projection.controller.kind, current);
693
707
  for (let index = 0; index < projection.endpoints.length; index++) for (let previous = 0; previous < index; previous++) {
694
708
  requireNetwork(!networkOverlaps(projection.endpoints[index].lease.ipv4.linkSubnet, projection.endpoints[previous].lease.ipv4.linkSubnet));
695
709
  }
@@ -792,6 +806,7 @@ export const admitRuntimeNetworkProjection = async (
792
806
  && (projection.router?.withdrawals.length ?? 0) === 0 && (projection.host?.withdrawals.length ?? 0) === 0
793
807
  && (projection.workloadIngress?.withdrawals.length ?? 0) === 0
794
808
  && projection.retiredLeases.length === 0 && projection.retiredHandoffs.length === 0);
809
+ if (projection.retirement) requireFirstProjectionRetirement(projection.retirement, projection.protectedAuthority);
795
810
  return { status: 'accepted', projection };
796
811
  }
797
812
  const previous = await validateRuntimeNetworkProjection(previousInput);
@@ -881,6 +896,10 @@ export const admitRuntimeNetworkProjection = async (
881
896
  }
882
897
  if (previous.handoff && !networkEqual(previous.handoff, projection.handoff)) requireNetwork(projection.retiredHandoffs.some(item =>
883
898
  networkEqual(item, networkReferenceOf(previous.handoff!))));
899
+ // A retirement request first names the current authority, then is never taken back or
900
+ // rewritten: every successor carries it unchanged.
901
+ if (previous.retirement) requireNetwork(networkEqual(projection.retirement ?? null, previous.retirement));
902
+ else if (projection.retirement) requireFirstProjectionRetirement(projection.retirement, projection.protectedAuthority);
884
903
  const protection = projection.protectedAuthority, oldProtection = previous.protectedAuthority;
885
904
  requireNetwork(networkEqual(protection, oldProtection) || (protection.id === oldProtection.id
886
905
  && networkEqual(protection.previous, networkReferenceOf(oldProtection))));
@@ -0,0 +1,72 @@
1
+ import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
2
+ import type { IRuntimeNetworkProtectedAuthority } from './networkauthority.js';
3
+ import { captureNetwork, networkEqual, networkRecord, networkReference, networkReferenceOf, networkSafe,
4
+ requireNetwork } from '../private/networkcontract.js';
5
+
6
+ /**
7
+ * Cloudly's request that a running node withdraw its allocation protection, because its egress
8
+ * ownership is being retired with the `withdrawn` disposition (`INodeRuntimeRetirement`).
9
+ *
10
+ * It travels as `IRuntimeNetworkProjection.retirement`: the one authenticated, monotonic channel a
11
+ * node already admits and journals on every pass. The controller's signature covers it, so a node
12
+ * cannot invent it; the projection chain is bound to the controller epoch, node and runtime
13
+ * namespace, so it cannot be carried to another node or controller; and once a projection carries
14
+ * it, every successor carries it unchanged, so it is never taken back. A retired node never
15
+ * returns (`runtimeNetworkEgressRetirementContract.reentry`): no later chain of the same node can
16
+ * meet a stale signal.
17
+ *
18
+ * The node answers with the null-barrier successor of its protection receipt chain
19
+ * (`IRuntimeNetworkProtectionReceipt.nativeBarrier: null`) and mints no positive receipt for that
20
+ * chain again. Its native allocation-pool guard stays enforced; the answer withdraws eligibility,
21
+ * not protection. Nothing else withdraws: a clean stop, a crash, a reboot or being offline keeps the
22
+ * node's last receipt current.
23
+ */
24
+ export interface IRuntimeNetworkProjectionRetirement {
25
+ /** The retirement row's disposition. A `lost` node never reports again, so it is never asked. */
26
+ disposition: 'withdrawn';
27
+ /**
28
+ * The protected authority current when Cloudly first signed the request: the projection that
29
+ * first carries it names exactly its own `protectedAuthority`. Its `id` names the authority chain
30
+ * the node's receipts belong to; the value then stays fixed while later projections carry a newer
31
+ * authority of the same chain, never an older one.
32
+ */
33
+ protectedAuthority: IRuntimeAuthorityReference;
34
+ }
35
+
36
+ export const runtimeNetworkProjectionRetirementContract = Object.freeze({
37
+ controllerKind: 'cloudly',
38
+ disposition: 'withdrawn',
39
+ firstAppearance: 'names-the-current-protected-authority',
40
+ latch: 'every-successor-carries-it-unchanged',
41
+ answer: 'null-native-barrier-receipt-successor',
42
+ positive: 'never-again-for-the-retiring-receipt-chain',
43
+ } as const);
44
+
45
+ /** Exact-key structural snapshot, detached from its input. */
46
+ export const snapshotRuntimeNetworkProjectionRetirement = (value: unknown): IRuntimeNetworkProjectionRetirement => networkSafe(() => {
47
+ const item = networkRecord(captureNetwork(value), ['disposition', 'protectedAuthority']);
48
+ requireNetwork(item.disposition === runtimeNetworkProjectionRetirementContract.disposition);
49
+ return { disposition: item.disposition, protectedAuthority: networkReference(item.protectedAuthority) };
50
+ });
51
+
52
+ /**
53
+ * The request belongs to a `cloudly` projection and names the projection's own authority chain at
54
+ * a generation the projection has reached: the current authority itself, or one of its predecessors.
55
+ */
56
+ export const requireProjectionRetirement = (
57
+ retirement: IRuntimeNetworkProjectionRetirement, controllerKind: string, current: IRuntimeNetworkProtectedAuthority,
58
+ ): void => {
59
+ const named = retirement.protectedAuthority;
60
+ requireNetwork(controllerKind === runtimeNetworkProjectionRetirementContract.controllerKind
61
+ && named.id === current.id && named.generation <= current.generation);
62
+ if (named.generation === current.generation) requireNetwork(networkEqual(named, networkReferenceOf(current)));
63
+ };
64
+
65
+ /**
66
+ * The request's first appearance names exactly the projection's current authority. An older
67
+ * generation would make the node's positive receipt under the current authority newer than the
68
+ * request, so the receipt gate refuses it and the withdrawal could never complete.
69
+ */
70
+ export const requireFirstProjectionRetirement = (
71
+ retirement: IRuntimeNetworkProjectionRetirement, current: IRuntimeNetworkProtectedAuthority,
72
+ ): void => requireNetwork(networkEqual(retirement.protectedAuthority, networkReferenceOf(current)));