@serve.zone/interfaces 32.44.0 → 32.46.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 +57 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/clusteringress.d.ts +37 -0
- package/dist_ts/data/clusteringress.js +43 -1
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -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 +58 -1
- package/dist_ts/data/serviceruntime.js +45 -2
- package/dist_ts/requests/network.d.ts +76 -1
- package/package.json +4 -4
- package/readme.md +54 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/clusteringress.ts +70 -0
- package/ts/data/index.ts +1 -0
- package/ts/data/runtimeassignment.ts +33 -5
- package/ts/data/serviceplatformownership.ts +95 -0
- package/ts/data/serviceruntime.ts +78 -1
- package/ts/requests/network.ts +65 -1
|
@@ -164,6 +164,15 @@ export const runtimeImagePullRefusalReasons = ['unauthorized', 'not-found', 'tls
|
|
|
164
164
|
'unavailable', 'other'] as const;
|
|
165
165
|
export type TRuntimeImagePullRefusalReason = typeof runtimeImagePullRefusalReasons[number];
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Why a node refused to start a run before any runtime effect of it, as the node names it:
|
|
169
|
+
* `unsupported-readiness` a readiness probe the run's network cannot carry (a TCP or HTTP probe of
|
|
170
|
+
* a run without network authority), `unresolved-authority` a workload authority the node cannot
|
|
171
|
+
* resolve, and `invalid` an assignment or configuration the node cannot read.
|
|
172
|
+
*/
|
|
173
|
+
export const runtimeAssignmentRefusalReasons = ['unsupported-readiness', 'unresolved-authority', 'invalid'] as const;
|
|
174
|
+
export type TRuntimeAssignmentRefusalReason = typeof runtimeAssignmentRefusalReasons[number];
|
|
175
|
+
|
|
167
176
|
/**
|
|
168
177
|
* Why a run failed, as its node can name it. Value-free: it carries a cause word and at most a
|
|
169
178
|
* bounded status number, never a runtime message, a registry, a reference or a path.
|
|
@@ -176,17 +185,31 @@ export type TRuntimeImagePullRefusalReason = typeof runtimeImagePullRefusalReaso
|
|
|
176
185
|
* - `runtime-absent`: the runtime holds no sandbox or container of a run that should be running.
|
|
177
186
|
* - `network-epoch-ended`: the run's network attachment belongs to a network epoch that has ended,
|
|
178
187
|
* so the node stopped it rather than leave it running without its network.
|
|
188
|
+
* - `refused`: the node refused the run before any runtime effect of it, for `reason`, so the
|
|
189
|
+
* observation carries no runtime identity.
|
|
190
|
+
* - `container-refused`: the runtime deterministically refused to create the run's container
|
|
191
|
+
* (CRI `CreateContainer`), so no container of the run exists and the observation carries no
|
|
192
|
+
* runtime identity. `grpcCode` is the runtime's gRPC status code (1..16).
|
|
179
193
|
*
|
|
180
|
-
* The cause explains the failure
|
|
181
|
-
*
|
|
194
|
+
* The cause explains the failure. Every failed run is replaced the same way, whichever cause it
|
|
195
|
+
* states, with one exception: a `refused` run is never re-placed with an identical configuration,
|
|
196
|
+
* because the node refuses the same input the same way. The controller places a new attempt only
|
|
197
|
+
* after the service's runtime spec or another input of the assignment changed.
|
|
182
198
|
*/
|
|
183
199
|
export type TRuntimeAssignmentFailureCause =
|
|
184
200
|
| { kind: 'image-pull-refused'; reason: TRuntimeImagePullRefusalReason; grpcCode: number }
|
|
185
201
|
| { kind: 'exited'; exitCode: number | null }
|
|
186
202
|
| { kind: 'runtime-absent' }
|
|
187
|
-
| { kind: 'network-epoch-ended' }
|
|
203
|
+
| { kind: 'network-epoch-ended' }
|
|
204
|
+
| { kind: 'refused'; reason: TRuntimeAssignmentRefusalReason }
|
|
205
|
+
| { kind: 'container-refused'; grpcCode: number };
|
|
188
206
|
export const runtimeAssignmentFailureCauseKinds = ['image-pull-refused', 'exited', 'runtime-absent',
|
|
189
|
-
'network-epoch-ended'] as const satisfies readonly TRuntimeAssignmentFailureCause['kind'][];
|
|
207
|
+
'network-epoch-ended', 'refused', 'container-refused'] as const satisfies readonly TRuntimeAssignmentFailureCause['kind'][];
|
|
208
|
+
|
|
209
|
+
/** The causes of a run of which nothing ever existed, so their observation names no runtime identity. */
|
|
210
|
+
const causesWithoutRuntime: readonly TRuntimeAssignmentFailureCause['kind'][] = [
|
|
211
|
+
'image-pull-refused', 'refused', 'container-refused',
|
|
212
|
+
];
|
|
190
213
|
|
|
191
214
|
/** Value-free execution evidence. Receipt/acceptance alone never means ready. */
|
|
192
215
|
export interface IRuntimeAssignmentObservation {
|
|
@@ -483,6 +506,11 @@ const isFailureCause = (valueArg: unknown): valueArg is TRuntimeAssignmentFailur
|
|
|
483
506
|
case 'runtime-absent':
|
|
484
507
|
case 'network-epoch-ended':
|
|
485
508
|
return isExactRecord(valueArg, ['kind']);
|
|
509
|
+
case 'refused':
|
|
510
|
+
return isExactRecord(valueArg, ['kind', 'reason'])
|
|
511
|
+
&& runtimeAssignmentRefusalReasons.some((reasonArg) => reasonArg === valueArg.reason);
|
|
512
|
+
case 'container-refused':
|
|
513
|
+
return isExactRecord(valueArg, ['kind', 'grpcCode']) && isBoundedInteger(valueArg.grpcCode, 1, 16);
|
|
486
514
|
default:
|
|
487
515
|
return false;
|
|
488
516
|
}
|
|
@@ -508,7 +536,7 @@ const assertObservation = (valueArg: unknown): IRuntimeAssignmentObservation =>
|
|
|
508
536
|
fail('runtime observation readiness and identity contradict its phase');
|
|
509
537
|
}
|
|
510
538
|
if (Object.hasOwn(valueArg, 'cause') && (valueArg.phase !== 'failed' || !isFailureCause(valueArg.cause)
|
|
511
|
-
|| (valueArg.cause.kind
|
|
539
|
+
|| (causesWithoutRuntime.includes(valueArg.cause.kind) && valueArg.runtime !== null))) {
|
|
512
540
|
fail('runtime observation failure cause must be exact and belong to a failed run');
|
|
513
541
|
}
|
|
514
542
|
canonical(valueArg);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { captureNetwork, networkCounter, networkId, networkRecord, requireNetwork } from '../private/networkcontract.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which authority writes a service's runtime network policy, decided by whether the service is
|
|
5
|
+
* platform-owned.
|
|
6
|
+
*
|
|
7
|
+
* A service of an idp.global organization is that organization's: its published ports are written
|
|
8
|
+
* only by a federated sign-in holding the organization's freshly introspected `owner` role, and an
|
|
9
|
+
* infrastructure administrator does not substitute for it. A platform-owned service belongs to no
|
|
10
|
+
* idp.global organization — the cluster ingress, a platform call router — so no organization owner
|
|
11
|
+
* exists who could authorize it. An infrastructure administrator designates such a service
|
|
12
|
+
* platform-owned, and from then on only a verified administrator reads and writes its published
|
|
13
|
+
* ports, as only an administrator writes its egress and its runtime spec. The designation names
|
|
14
|
+
* the authority and nothing else: it grants no port, egress or placement by itself.
|
|
15
|
+
*
|
|
16
|
+
* Only a service of a platform organization (`isPlatformOrganizationId`) is ever designated, so the
|
|
17
|
+
* federated owner rule stays the only rule for every other service, including any whose
|
|
18
|
+
* organization id is not a canonical identifier at all. Withdrawing the designation hands the
|
|
19
|
+
* ports back to the organization-owner rule, and a platform organization has no idp.global owner:
|
|
20
|
+
* the service's published ports then have no writer, and stay as they are, until an administrator
|
|
21
|
+
* designates it again.
|
|
22
|
+
*/
|
|
23
|
+
export interface IServicePlatformOwnership {
|
|
24
|
+
organizationId: string;
|
|
25
|
+
serviceId: string;
|
|
26
|
+
/** The administrator who designated the service. */
|
|
27
|
+
actorId: string;
|
|
28
|
+
/** Unix milliseconds on Cloudly's clock. */
|
|
29
|
+
designatedAt: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Every reason Cloudly answers instead of designating a service platform-owned or withdrawing the
|
|
34
|
+
* designation. Each travels by name as a `TypedResponseError`, and a refused request changes
|
|
35
|
+
* nothing.
|
|
36
|
+
*/
|
|
37
|
+
export const servicePlatformOwnershipRefusals = Object.freeze([
|
|
38
|
+
/** No service of that id belongs to the named organization. */
|
|
39
|
+
'service-unknown',
|
|
40
|
+
/** The service's deletion has begun; no new authority outlives it. */
|
|
41
|
+
'service-deleting',
|
|
42
|
+
/**
|
|
43
|
+
* The named organization is not a platform organization (`isPlatformOrganizationId`): an
|
|
44
|
+
* idp.global organization, whose owner keeps the authority over its runtime network policy, or an
|
|
45
|
+
* id that is no canonical identifier.
|
|
46
|
+
*/
|
|
47
|
+
'organization-not-platform',
|
|
48
|
+
/** Another write changed the service's designation first: read it again. */
|
|
49
|
+
'conflict',
|
|
50
|
+
] as const);
|
|
51
|
+
|
|
52
|
+
/** One of the reasons Cloudly answers instead of a designation change. */
|
|
53
|
+
export type TServicePlatformOwnershipRefusal = typeof servicePlatformOwnershipRefusals[number];
|
|
54
|
+
|
|
55
|
+
export const servicePlatformOwnershipContract = Object.freeze({
|
|
56
|
+
error: 'Service platform ownership contract rejected.',
|
|
57
|
+
} as const);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether an organization id names a platform organization, the only kind whose service an
|
|
61
|
+
* administrator may designate platform-owned.
|
|
62
|
+
*
|
|
63
|
+
* Every organization Cloudly assigns outside idp.global is namespaced with `:` — a legacy service's
|
|
64
|
+
* own `legacy:Service:<id>`, a platform `Organization:<id>` — while idp.global names an
|
|
65
|
+
* organization by a 21-character nanoid in the URL alphabet (`A-Za-z0-9_-`), which never carries
|
|
66
|
+
* `:`. An id is a platform organization's exactly when it is a canonical identifier (an ASCII
|
|
67
|
+
* letter or digit, then up to 199 of letters, digits, `:`, `.`, `_` and `-`) and carries `:`.
|
|
68
|
+
* Everything else is not: an idp.global id, whichever character it starts with, and any value that
|
|
69
|
+
* is not a canonical identifier — empty, whitespace, a non-string. The answer fails closed: an id
|
|
70
|
+
* this rule cannot read never gains administrator authority.
|
|
71
|
+
*/
|
|
72
|
+
export const isPlatformOrganizationId = (organizationId: string): boolean => {
|
|
73
|
+
try {
|
|
74
|
+
return networkId(organizationId).includes(':');
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Detached exact-schema snapshot of a stored or answered designation. */
|
|
81
|
+
export const snapshotServicePlatformOwnership = (value: unknown): IServicePlatformOwnership => {
|
|
82
|
+
try {
|
|
83
|
+
const item = networkRecord(captureNetwork(value), ['organizationId', 'serviceId', 'actorId', 'designatedAt']);
|
|
84
|
+
const organizationId = networkId(item.organizationId);
|
|
85
|
+
requireNetwork(isPlatformOrganizationId(organizationId));
|
|
86
|
+
return {
|
|
87
|
+
organizationId,
|
|
88
|
+
serviceId: networkId(item.serviceId),
|
|
89
|
+
actorId: networkId(item.actorId),
|
|
90
|
+
designatedAt: networkCounter(item.designatedAt),
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error(servicePlatformOwnershipContract.error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
@@ -91,7 +91,8 @@ const serviceRuntimeRefusals = [
|
|
|
91
91
|
'volume-placement-unsupported', 'volume-initialization-unknown', 'volume-ownership-unknown',
|
|
92
92
|
'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
|
|
93
93
|
'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
|
|
94
|
-
'published-port-reserved', 'published-port-symmetric-without-egress',
|
|
94
|
+
'published-port-reserved', 'published-port-symmetric-without-egress', 'readiness-network-unsupported',
|
|
95
|
+
'cluster-relay-missing', 'cluster-ingress-environment-conflict',
|
|
95
96
|
] as const;
|
|
96
97
|
|
|
97
98
|
/**
|
|
@@ -158,6 +159,22 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
|
|
|
158
159
|
* which the handoff leases' `runtimeNetworkHandoffSnatPortRange` owns; a document written before
|
|
159
160
|
* that rule can hold one. `published-port-symmetric-without-egress` is a `symmetric` entry in a
|
|
160
161
|
* document whose `publicEgress` is false, which a projection endpoint refuses.
|
|
162
|
+
*
|
|
163
|
+
* `readiness-network-unsupported` is a spec whose network is `isolated` and whose readiness probe is
|
|
164
|
+
* `tcp` or `http`: an isolated workload has no address its node can dial, so the probe could never
|
|
165
|
+
* pass and the node refuses the run (`TRuntimeAssignmentFailureCause` `refused` for
|
|
166
|
+
* `unsupported-readiness`). `isServiceRuntimeReadinessReachable` is this rule; a producer answers
|
|
167
|
+
* what it returns. The spec snapshot still reads such a spec, so a stored one stays readable and
|
|
168
|
+
* is answered with this refusal rather than refused as data.
|
|
169
|
+
*
|
|
170
|
+
* The two `cluster-` refusals name why the environment of a cluster's designated ingress service
|
|
171
|
+
* cannot be composed (`composeClusterIngressEnvironment`); a service no designation names is never
|
|
172
|
+
* judged by them. `cluster-relay-missing` is a cluster that states no valid relay
|
|
173
|
+
* (`IClusterRelay`): the ingress registers through its relay's exact origin, so without one it has
|
|
174
|
+
* nothing to start with, and an administrator enables the relay (`enableClusterRelay`).
|
|
175
|
+
* `cluster-ingress-environment-conflict` is an ingress service whose own environment already sets
|
|
176
|
+
* one of `clusterIngressEnvironmentNames`: Cloudly owns those names, and a service value would
|
|
177
|
+
* otherwise silently replace Cloudly's or be replaced by it.
|
|
161
178
|
*/
|
|
162
179
|
export type TServiceRuntimeRefusal = typeof serviceRuntimeRefusals[number];
|
|
163
180
|
|
|
@@ -184,6 +201,38 @@ export type IServiceRuntimeStatus = IServiceRuntimeStatusBase & (
|
|
|
184
201
|
*/
|
|
185
202
|
export type TServiceRuntimeAssignmentDelivery = 'undelivered' | 'delivered';
|
|
186
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Why one dispatch of an assignment revision to its node ended as it did.
|
|
206
|
+
*
|
|
207
|
+
* The node holds the revision: `accepted`, `replay` or `historical` is what the node answered to
|
|
208
|
+
* the push, and `acknowledged` a revision the node's current session acknowledged before, answered
|
|
209
|
+
* from that acknowledgement without pushing it again.
|
|
210
|
+
*
|
|
211
|
+
* The node does not hold it from this dispatch: `node-busy` while the node already carries its limit
|
|
212
|
+
* of pushes in flight, `node-not-connected` while no live peer holds a session for the node,
|
|
213
|
+
* `session-moved` when the session the dispatch began on is no longer the node's current one,
|
|
214
|
+
* `intent-not-current` when the revision is no longer the one the controller wants on that node,
|
|
215
|
+
* `request-unbound` when the request does not bind to the session it would travel on,
|
|
216
|
+
* `node-refused` when the node (or the relay carrying it) answered the push with a refusal,
|
|
217
|
+
* `push-failed` when the push got no answer in time or its carrier failed, and
|
|
218
|
+
* `acknowledgement-unbound` when the answer names another revision.
|
|
219
|
+
*/
|
|
220
|
+
export const assignmentDispatchReasons = [
|
|
221
|
+
'accepted', 'replay', 'historical', 'acknowledged',
|
|
222
|
+
'node-busy', 'node-not-connected', 'session-moved', 'intent-not-current', 'request-unbound', 'node-refused',
|
|
223
|
+
'push-failed', 'acknowledgement-unbound',
|
|
224
|
+
] as const;
|
|
225
|
+
export type TAssignmentDispatchReason = typeof assignmentDispatchReasons[number];
|
|
226
|
+
|
|
227
|
+
/** The last dispatch of a slot's current assignment, as the controller recorded it. Value-free. */
|
|
228
|
+
export interface IServiceRuntimeAssignmentDispatch {
|
|
229
|
+
/** Whether the node holds the revision after this dispatch. */
|
|
230
|
+
delivered: boolean;
|
|
231
|
+
reason: TAssignmentDispatchReason;
|
|
232
|
+
/** Unix milliseconds on the controller's clock when this dispatch ended. */
|
|
233
|
+
at: number;
|
|
234
|
+
}
|
|
235
|
+
|
|
187
236
|
/** One replica slot's current assignment, as an administrator reads it. */
|
|
188
237
|
export interface IServiceRuntimeAssignmentView {
|
|
189
238
|
serviceId: string;
|
|
@@ -200,6 +249,12 @@ export interface IServiceRuntimeAssignmentView {
|
|
|
200
249
|
/** Joined stop and remove receipts of this attempt. */
|
|
201
250
|
terminalReceiptCount: 0 | 1 | 2;
|
|
202
251
|
delivery: TServiceRuntimeAssignmentDelivery;
|
|
252
|
+
/**
|
|
253
|
+
* The last dispatch of the current revision while the controller still remembers one, null when
|
|
254
|
+
* it remembers none. Absent from a controller that does not report dispatches. Dispatch is the
|
|
255
|
+
* controller's push; `delivery` stays the node's own evidence.
|
|
256
|
+
*/
|
|
257
|
+
dispatch?: IServiceRuntimeAssignmentDispatch | null;
|
|
203
258
|
}
|
|
204
259
|
|
|
205
260
|
/** Sorted, duplicate-free and read by the vocabulary the ids belong to: a node's, or the canonical one. */
|
|
@@ -264,6 +319,28 @@ export const snapshotServiceRuntimeSpec = (value: unknown): IServiceRuntimeSpec
|
|
|
264
319
|
profile: profileOf(item.profile), network: networkOf(item.network), revision: networkCounter(item.revision) };
|
|
265
320
|
});
|
|
266
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Whether a spec's readiness probe can reach its workload: false exactly for an `isolated` network
|
|
324
|
+
* with a `tcp` or `http` probe, which the producer answers as `readiness-network-unsupported`. The
|
|
325
|
+
* spec is read through `snapshotServiceRuntimeSpec`, so a value that is no spec is refused with its
|
|
326
|
+
* rejection rather than judged.
|
|
327
|
+
*/
|
|
328
|
+
export const isServiceRuntimeReadinessReachable = (value: unknown): boolean => {
|
|
329
|
+
const { profile, network } = snapshotServiceRuntimeSpec(value);
|
|
330
|
+
return network.mode === 'attached' || profile.readiness.kind === 'process';
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** Exactly a recorded dispatch of an assignment: a known reason and a positive Unix millisecond time. */
|
|
334
|
+
export const isServiceRuntimeAssignmentDispatch = (value: unknown): value is IServiceRuntimeAssignmentDispatch => {
|
|
335
|
+
try {
|
|
336
|
+
const item = networkRecord(captureNetwork(value), ['delivered', 'reason', 'at']);
|
|
337
|
+
networkCounter(item.at);
|
|
338
|
+
return typeof item.delivered === 'boolean' && isOneOf(assignmentDispatchReasons, item.reason);
|
|
339
|
+
} catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
267
344
|
/** Structural snapshot for exact persistence: a reason belongs to exactly one state. */
|
|
268
345
|
export const snapshotServiceRuntimeStatus = (value: unknown): IServiceRuntimeStatus => producerSafe(() => {
|
|
269
346
|
const item = networkRecord(captureNetwork(value), ['id', 'specRevision', 'state', 'reason', 'changedAt']);
|
package/ts/requests/network.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import type {
|
|
10
10
|
IServiceNetworkEgress, IServicePublishedPorts, ISetServiceNetworkEgress, ISetServicePublishedPorts,
|
|
11
11
|
} from '../data/networkprojection.js';
|
|
12
|
+
import type { IServicePlatformOwnership } from '../data/serviceplatformownership.js';
|
|
12
13
|
|
|
13
14
|
/** Current organization picker data. Entries are never cached authorization grants. */
|
|
14
15
|
export interface IReq_Cloudly_ListOwnedOrganizations extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -73,7 +74,14 @@ export interface IReq_Cloudly_SetServicePrivateNetworks extends plugins.typedreq
|
|
|
73
74
|
response: { membership: IServicePrivateNetworks; replayed: boolean };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* null means the service has never had a policy document, neither published ports nor egress.
|
|
79
|
+
*
|
|
80
|
+
* The authority follows the service (`IServicePlatformOwnership`): a platform-owned service is read
|
|
81
|
+
* by a verified administrator, every other service by a federated sign-in holding the named
|
|
82
|
+
* organization's freshly introspected `owner` role. A caller without that authority, or naming no
|
|
83
|
+
* such service, is answered one denial text, whichever it is.
|
|
84
|
+
*/
|
|
77
85
|
export interface IReq_Cloudly_GetServicePublishedPorts extends plugins.typedrequestInterfaces.implementsTR<
|
|
78
86
|
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetServicePublishedPorts
|
|
79
87
|
> {
|
|
@@ -92,6 +100,15 @@ export interface IReq_Cloudly_GetServicePublishedPorts extends plugins.typedrequ
|
|
|
92
100
|
* attached spec stating `publicEgress: false` and `platformEndpointIds: []` matches it and reaches
|
|
93
101
|
* no public address and no platform endpoint; `network-policy-missing` means the service has no
|
|
94
102
|
* document at all.
|
|
103
|
+
*
|
|
104
|
+
* The authority follows the service (`IServicePlatformOwnership`): a platform-owned service is
|
|
105
|
+
* written by a verified administrator, every other service by a federated sign-in holding the named
|
|
106
|
+
* organization's freshly introspected `owner` role, and an administrator does not substitute for
|
|
107
|
+
* that owner. A caller without that authority, or naming no such service, is answered one denial
|
|
108
|
+
* text, whichever it is. A designation that changes while the write is staged refuses it as the
|
|
109
|
+
* policy write's own `conflict`, the refusal a stale `expectedRevision` is also answered (read the
|
|
110
|
+
* document and the authority again); it is a service network policy refusal, not one of
|
|
111
|
+
* `servicePlatformOwnershipRefusals`.
|
|
95
112
|
*/
|
|
96
113
|
export interface IReq_Cloudly_SetServicePublishedPorts extends plugins.typedrequestInterfaces.implementsTR<
|
|
97
114
|
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_SetServicePublishedPorts
|
|
@@ -147,6 +164,53 @@ export interface IReq_Admin_Cloudly_SetServiceNetworkEgress extends plugins.type
|
|
|
147
164
|
response: { networkEgress: IServiceNetworkEgress; replayed: boolean };
|
|
148
165
|
}
|
|
149
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Designates one service platform-owned (`IServicePlatformOwnership`), so that a verified
|
|
169
|
+
* administrator, and no organization owner, holds the authority over its published ports.
|
|
170
|
+
*
|
|
171
|
+
* Only a verified administrator calls it. Cloudly designates only a service of a platform
|
|
172
|
+
* organization (`isPlatformOrganizationId`: a canonical identifier carrying `:`, such as
|
|
173
|
+
* `legacy:Service:<id>` or `Organization:<id>`) and refuses every other organization id, an
|
|
174
|
+
* idp.global one or one that is no canonical identifier, as `organization-not-platform` before it
|
|
175
|
+
* reads the service. It refuses a service that is not the named organization's (`service-unknown`)
|
|
176
|
+
* and a deleting one (`service-deleting`); refusals are `servicePlatformOwnershipRefusals`. A
|
|
177
|
+
* service already designated answers its stored designation unchanged with `changed: false`.
|
|
178
|
+
*/
|
|
179
|
+
export interface IReq_Admin_Cloudly_DesignatePlatformOwnedService extends plugins.typedrequestInterfaces.implementsTR<
|
|
180
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_DesignatePlatformOwnedService
|
|
181
|
+
> {
|
|
182
|
+
method: 'designatePlatformOwnedService';
|
|
183
|
+
request: { identity: IIdentityCredential; organizationId: string; serviceId: string };
|
|
184
|
+
response: { platformOwnership: IServicePlatformOwnership; changed: boolean };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Withdraws a service's platform-owned designation; its published ports stay as they are, and from
|
|
189
|
+
* then on the organization-owner rule governs them. A designated service is always a platform
|
|
190
|
+
* organization's, which has no idp.global owner, so after the withdrawal nobody writes or reads its
|
|
191
|
+
* published ports until an administrator designates it again: the authority fails closed.
|
|
192
|
+
*
|
|
193
|
+
* Only a verified administrator calls it. Refusals are `service-unknown` and `conflict`; a service
|
|
194
|
+
* without a designation answers `changed: false`.
|
|
195
|
+
*/
|
|
196
|
+
export interface IReq_Admin_Cloudly_UndesignatePlatformOwnedService extends plugins.typedrequestInterfaces.implementsTR<
|
|
197
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_UndesignatePlatformOwnedService
|
|
198
|
+
> {
|
|
199
|
+
method: 'undesignatePlatformOwnedService';
|
|
200
|
+
request: { identity: IIdentityCredential; organizationId: string; serviceId: string };
|
|
201
|
+
response: { platformOwnership: null; changed: boolean };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The service's platform-owned designation, or null when it has none. Only a verified administrator reads it. */
|
|
205
|
+
export interface IReq_Admin_Cloudly_GetServicePlatformOwnership extends plugins.typedrequestInterfaces.implementsTR<
|
|
206
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_Cloudly_GetServicePlatformOwnership
|
|
207
|
+
> {
|
|
208
|
+
method: 'getServicePlatformOwnership';
|
|
209
|
+
/** Refused `service-unknown` when no service of that id belongs to the named organization. */
|
|
210
|
+
request: { identity: IIdentityCredential; organizationId: string; serviceId: string };
|
|
211
|
+
response: { platformOwnership: IServicePlatformOwnership | null };
|
|
212
|
+
}
|
|
213
|
+
|
|
150
214
|
export interface IRequest_Any_Cloudly_GetNetworkNodes {
|
|
151
215
|
method: 'getNetworkNodes';
|
|
152
216
|
request: {};
|