@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.
- package/changelog.md +65 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +3 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/networkegressretirement.d.ts +2 -1
- package/dist_ts/data/networkegressretirement.js +1 -1
- package/dist_ts/data/networkprojection.d.ts +10 -0
- package/dist_ts/data/networkprojection.js +15 -2
- package/dist_ts/data/networkprojectionretirement.d.ts +52 -0
- package/dist_ts/data/networkprojectionretirement.js +33 -0
- package/dist_ts/data/networkprotection.d.ts +29 -1
- package/dist_ts/data/networkprotection.js +45 -1
- package/dist_ts/data/noderetirement.d.ts +3 -1
- package/dist_ts/data/noderetirement.js +1 -1
- package/dist_ts/data/runtimeassignment.d.ts +24 -3
- package/dist_ts/data/runtimeassignment.js +19 -3
- package/dist_ts/data/serviceplatformownership.d.ts +55 -0
- package/dist_ts/data/serviceplatformownership.js +62 -0
- package/dist_ts/data/serviceruntime.d.ts +49 -1
- package/dist_ts/data/serviceruntime.js +44 -2
- package/dist_ts/requests/network.d.ts +76 -1
- package/package.json +5 -5
- package/readme.md +98 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +3 -0
- package/ts/data/networkegressretirement.ts +2 -1
- package/ts/data/networkprojection.ts +20 -1
- package/ts/data/networkprojectionretirement.ts +72 -0
- package/ts/data/networkprotection.ts +52 -1
- package/ts/data/noderetirement.ts +3 -1
- package/ts/data/runtimeassignment.ts +33 -5
- package/ts/data/serviceplatformownership.ts +95 -0
- package/ts/data/serviceruntime.ts +68 -1
- 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,
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
31
|
-
"@git.zone/tsdoc": "^3.0.
|
|
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": "
|
|
34
|
-
"@types/node": "26.6.
|
|
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
|
|
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
|
|
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
|
|
1931
|
-
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
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
|
|
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)));
|