@serve.zone/interfaces 32.59.0 → 32.61.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 (37) hide show
  1. package/.smartconfig.json +4 -2
  2. package/changelog.md +87 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/data/delegatedadmintoken.d.ts +142 -0
  5. package/dist_ts/data/delegatedadmintoken.js +197 -0
  6. package/dist_ts/data/externalregistry.d.ts +37 -1
  7. package/dist_ts/data/gateway.d.ts +23 -4
  8. package/dist_ts/data/gateway.js +1 -1
  9. package/dist_ts/data/index.d.ts +2 -0
  10. package/dist_ts/data/index.js +3 -1
  11. package/dist_ts/data/launchersecretowner.d.ts +60 -0
  12. package/dist_ts/data/launchersecretowner.js +94 -0
  13. package/dist_ts/data/secret.d.ts +67 -1
  14. package/dist_ts/data/secret.js +59 -6
  15. package/dist_ts/data/serviceruntime.d.ts +9 -2
  16. package/dist_ts/data/serviceruntime.js +2 -1
  17. package/dist_ts/platform/objectstorageenvironment.d.ts +12 -3
  18. package/dist_ts/platform/objectstorageenvironment.js +22 -6
  19. package/dist_ts/requests/admin.d.ts +52 -0
  20. package/dist_ts/requests/externalregistry.d.ts +12 -6
  21. package/dist_ts/requests/identity.d.ts +4 -0
  22. package/dist_ts/requests/service.d.ts +16 -1
  23. package/package.json +5 -8
  24. package/readme.md +76 -12
  25. package/ts/00_commitinfo_data.ts +1 -1
  26. package/ts/data/delegatedadmintoken.ts +282 -0
  27. package/ts/data/externalregistry.ts +60 -2
  28. package/ts/data/gateway.ts +24 -3
  29. package/ts/data/index.ts +2 -0
  30. package/ts/data/launchersecretowner.ts +145 -0
  31. package/ts/data/secret.ts +103 -7
  32. package/ts/data/serviceruntime.ts +9 -1
  33. package/ts/platform/objectstorageenvironment.ts +26 -8
  34. package/ts/requests/admin.ts +51 -0
  35. package/ts/requests/externalregistry.ts +12 -6
  36. package/ts/requests/identity.ts +4 -0
  37. package/ts/requests/service.ts +16 -1
package/ts/data/secret.ts CHANGED
@@ -228,7 +228,33 @@ export interface ISecretSetConsumerRolloutStatus {
228
228
  updatedAt: number;
229
229
  }
230
230
 
231
+ /**
232
+ * A launcher-environment delivery as a caller states it: on a Secret's `defaultDelivery`, a
233
+ * SecretSet attachment override, a create request or an App Store declaration. The owner of the
234
+ * file WorkloadInit reads is not the caller's to state; the resolver derives it.
235
+ */
231
236
  export interface ISecretLauncherEnvironmentDelivery {
237
+ type: 'launcher-environment';
238
+ variableName: string;
239
+ /**
240
+ * @deprecated Derived by the resolver from the workload identity
241
+ * (`deriveLauncherSecretDeliveryOwner`); ignored; removed in the next major.
242
+ */
243
+ uid?: number;
244
+ /**
245
+ * @deprecated Derived by the resolver from the workload identity
246
+ * (`deriveLauncherSecretDeliveryOwner`); ignored; removed in the next major.
247
+ */
248
+ gid?: number;
249
+ mode: 0o400 | 0o440;
250
+ }
251
+
252
+ /**
253
+ * A launcher-environment delivery of a resolved secret manifest. `uid` and `gid` are the owner the
254
+ * resolver derived from the workload identity (`deriveLauncherSecretDeliveryOwner`), written by
255
+ * `createResolvedSecretDelivery`; the node mounts the file with them.
256
+ */
257
+ export interface ISecretResolvedLauncherEnvironmentDelivery {
232
258
  type: 'launcher-environment';
233
259
  variableName: string;
234
260
  uid: number;
@@ -244,8 +270,50 @@ export interface ISecretFileDelivery {
244
270
  mode: 0o400 | 0o440;
245
271
  }
246
272
 
273
+ /** A delivery as a caller states it. */
247
274
  export type TSecretDelivery = ISecretLauncherEnvironmentDelivery | ISecretFileDelivery;
248
275
 
276
+ /** A delivery of a resolved secret manifest: every owner is stated. */
277
+ export type TSecretResolvedDelivery = ISecretResolvedLauncherEnvironmentDelivery | ISecretFileDelivery;
278
+
279
+ /**
280
+ * Prefixes of the names the platform injects into a workload itself (`SERVEZONE_*`): a secret of
281
+ * such a name would replace what the platform delivers.
282
+ */
283
+ export const platformReservedSecretKeyPrefixes = Object.freeze(['SERVEZONE_'] as const);
284
+
285
+ /**
286
+ * Prefixes of the names a loader, language runtime, package manager or library reads to change
287
+ * what a process loads, runs or trusts (`LD_PRELOAD`, `NODE_OPTIONS`, `PYTHONPATH`, `OPENSSL_CONF`,
288
+ * `DOTNET_STARTUP_HOOKS`, `NPM_CONFIG_REGISTRY`, …). Matched as plain prefixes, so `PYTHON`, `PERL`,
289
+ * `RUBY` and `PHP` cover every name that starts with them.
290
+ */
291
+ export const runtimeInjectionSecretKeyPrefixes = Object.freeze([
292
+ 'LD_', 'DYLD_', 'NODE_', 'DENO_', 'BUN_', 'PYTHON', 'PERL', 'RUBY', 'PHP', 'JAVA_', 'JDK_', '_JAVA_', 'DOTNET_',
293
+ 'CORECLR_', 'LUA_', 'GEM_', 'BUNDLE_', 'NPM_CONFIG_', 'PIP_', 'GLIBC_', 'MALLOC_', 'GCONV_', 'OPENSSL_', 'SSL_',
294
+ 'CURL_', 'REQUESTS_', 'GIT_',
295
+ ] as const);
296
+
297
+ /**
298
+ * Exact names a shell, loader, runtime or HTTP client reads to change where a process looks,
299
+ * connects or what it trusts or records (`CLASSPATH`, `AWS_CA_BUNDLE`, `SSLKEYLOGFILE`).
300
+ */
301
+ export const runtimeInjectionSecretKeys = Object.freeze([
302
+ 'PATH', 'HOME', 'SHELL', 'ENV', 'BASH_ENV', 'IFS', 'TMPDIR', 'LOCPATH', 'NLSPATH', 'CLASSPATH', 'HTTP_PROXY',
303
+ 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'FTP_PROXY', 'AWS_CA_BUNDLE', 'SSLKEYLOGFILE',
304
+ ] as const);
305
+
306
+ /**
307
+ * Whether a secret key, or a name a secret is delivered under, is reserved: one of
308
+ * `runtimeInjectionSecretKeys`, or starting with an entry of `platformReservedSecretKeyPrefixes`
309
+ * or `runtimeInjectionSecretKeyPrefixes`. A delegated administrator token never writes such a name.
310
+ */
311
+ export const isReservedSecretKey = (keyArg: string): boolean => (
312
+ (runtimeInjectionSecretKeys as readonly string[]).includes(keyArg)
313
+ || platformReservedSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
314
+ || runtimeInjectionSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
315
+ );
316
+
249
317
  export const secretValueLimits = Object.freeze({
250
318
  maximumBytes: 500 * 1024,
251
319
  minimumGeneratedBytes: 16,
@@ -299,7 +367,7 @@ export interface IResolvedSecretManifestEntry {
299
367
  key: string;
300
368
  environment: TSecretEnvironment;
301
369
  owner: TWorkloadSecretOwner;
302
- delivery: TSecretDelivery;
370
+ delivery: TSecretResolvedDelivery;
303
371
  }
304
372
 
305
373
  /** Immutable, value-free runtime snapshot. */
@@ -709,14 +777,16 @@ export const validateSecretManagementScope = (
709
777
  : ['management scope prefix must match management source'];
710
778
  };
711
779
 
712
- export const validateSecretDelivery = (deliveryArg: unknown): string[] => {
780
+ const validateDelivery = (deliveryArg: unknown, launcherOwnerArg: 'tolerated' | 'required'): string[] => {
713
781
  if (!isRecord(deliveryArg)) return ['delivery must be an object'];
714
782
  const isLauncher = deliveryArg.type === 'launcher-environment';
715
783
  if (!isLauncher && deliveryArg.type !== 'file') {
716
784
  return ['delivery type must be launcher-environment or file'];
717
785
  }
786
+ const statesOwner = !isLauncher || launcherOwnerArg === 'required'
787
+ || deliveryArg.uid !== undefined || deliveryArg.gid !== undefined;
718
788
  const expectedKeys = isLauncher
719
- ? ['type', 'variableName', 'uid', 'gid', 'mode']
789
+ ? ['type', 'variableName', ...(statesOwner ? ['uid', 'gid'] : []), 'mode']
720
790
  : ['type', 'targetPath', 'uid', 'gid', 'mode'];
721
791
  if (!hasExactKeys(deliveryArg, expectedKeys)) {
722
792
  return ['delivery must use its exact schema'];
@@ -730,7 +800,7 @@ export const validateSecretDelivery = (deliveryArg: unknown): string[] => {
730
800
  || !/^\/run\/secrets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(deliveryArg.targetPath))) {
731
801
  errors.push('file delivery targetPath must be a canonical file below /run/secrets');
732
802
  }
733
- for (const field of ['uid', 'gid'] as const) {
803
+ for (const field of statesOwner ? ['uid', 'gid'] as const : []) {
734
804
  const value = deliveryArg[field];
735
805
  if (!Number.isSafeInteger(value)
736
806
  || (value as number) < 0
@@ -744,6 +814,30 @@ export const validateSecretDelivery = (deliveryArg: unknown): string[] => {
744
814
  return errors;
745
815
  };
746
816
 
817
+ /**
818
+ * Validates a delivery as a caller states it. A launcher delivery should state no owner; one that
819
+ * states the deprecated `uid` and `gid` (both, bounded) is tolerated until the next major.
820
+ */
821
+ export const validateSecretDelivery = (deliveryArg: unknown): string[] => validateDelivery(deliveryArg, 'tolerated');
822
+
823
+ /** Validates a resolved manifest's delivery: a launcher delivery states its derived owner. */
824
+ export const validateSecretResolvedDelivery = (deliveryArg: unknown): string[] => validateDelivery(
825
+ deliveryArg, 'required');
826
+
827
+ /**
828
+ * The resolved manifest delivery of a caller's delivery: a launcher delivery takes the owner the
829
+ * resolver derived (`deriveLauncherSecretDeliveryOwner`) and drops any deprecated owner the caller
830
+ * stated; a file delivery keeps its own.
831
+ */
832
+ export const createResolvedSecretDelivery = (
833
+ deliveryArg: TSecretDelivery,
834
+ ownerArg: { uid: number; gid: number },
835
+ ): TSecretResolvedDelivery => (deliveryArg.type === 'launcher-environment'
836
+ ? { type: 'launcher-environment', variableName: deliveryArg.variableName, uid: ownerArg.uid, gid: ownerArg.gid,
837
+ mode: deliveryArg.mode }
838
+ : { type: 'file', targetPath: deliveryArg.targetPath, uid: deliveryArg.uid, gid: deliveryArg.gid,
839
+ mode: deliveryArg.mode });
840
+
747
841
  export const validateSecretMetadata = (secretArg: unknown): string[] => {
748
842
  if (!isRecord(secretArg)) return ['secret metadata must be an object'];
749
843
  const expectedKeys = [
@@ -1269,7 +1363,7 @@ export const validateResolvedSecretManifest = (
1269
1363
  `secret manifest entries[${index}].owner serviceId must match the manifest serviceId`,
1270
1364
  );
1271
1365
  }
1272
- errors.push(...validateSecretDelivery(candidate.delivery).map(
1366
+ errors.push(...validateSecretResolvedDelivery(candidate.delivery).map(
1273
1367
  (errorArg) => `secret manifest entries[${index}].${errorArg}`,
1274
1368
  ));
1275
1369
  if (isRecord(candidate.delivery)) {
@@ -1281,7 +1375,7 @@ export const validateResolvedSecretManifest = (
1281
1375
  } else if (
1282
1376
  candidate.delivery.type === 'file'
1283
1377
  && typeof candidate.delivery.targetPath === 'string'
1284
- && validateSecretDelivery(candidate.delivery).length === 0
1378
+ && validateSecretResolvedDelivery(candidate.delivery).length === 0
1285
1379
  ) {
1286
1380
  fileDeliveryTargets.push(candidate.delivery.targetPath);
1287
1381
  }
@@ -1584,7 +1678,7 @@ export const createWorkloadInitEnvironmentMap = async (
1584
1678
  .map(async (entryArg) => {
1585
1679
  const resource = await createDockerSecretResourceName(entryArg.secretVersionId);
1586
1680
  return {
1587
- variableName: (entryArg.delivery as ISecretLauncherEnvironmentDelivery).variableName,
1681
+ variableName: (entryArg.delivery as ISecretResolvedLauncherEnvironmentDelivery).variableName,
1588
1682
  resource,
1589
1683
  source: createLauncherSecretSourcePath(resource),
1590
1684
  };
@@ -1818,6 +1912,8 @@ const canonicalSecretOwner = (ownerArg: TSecretOwner): Record<string, string> =>
1818
1912
  }
1819
1913
  };
1820
1914
 
1915
+ // A launcher delivery that still states the deprecated owner keeps the context it was sealed
1916
+ // under; JSON leaves an absent owner out.
1821
1917
  const canonicalSecretDelivery = (deliveryArg: TSecretDelivery): Record<string, unknown> => (
1822
1918
  deliveryArg.type === 'launcher-environment'
1823
1919
  ? {
@@ -89,6 +89,7 @@ const serviceRuntimeRefusals = [
89
89
  'network-workload-pool-missing', 'network-workload-pool-exhausted',
90
90
  'volume-driver-unsupported', 'volume-options-unsupported', 'volume-shared-unsupported',
91
91
  'volume-placement-unsupported', 'volume-initialization-unknown', 'volume-ownership-unknown',
92
+ 'launcher-secret-ownership-unknown',
92
93
  'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
93
94
  'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
94
95
  'published-port-reserved', 'published-port-symmetric-without-egress', 'readiness-network-unsupported',
@@ -128,11 +129,18 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
128
129
  * `volume-ownership-unknown` a `run-as` ownership under `runAs: null`
129
130
  * (`deriveServiceVolumeClaimInitialization`); neither is defaulted.
130
131
  *
132
+ * `launcher-secret-ownership-unknown` is a service with launcher-environment secrets whose runtime
133
+ * spec runs the image's own user (`runAs: null`) while that user's numeric owner is not known: a
134
+ * named non-root user, a numeric part above the identity bound, a group a `0o440` delivery would grant that the image does not state
135
+ * numerically, or platforms of the pinned index that derive different owners
136
+ * (`deriveLauncherSecretDeliveryOwner`). WorkloadInit could not read secret files owned by a guess.
137
+ *
131
138
  * The object-storage refusals name why a binding's environment cannot be composed
132
139
  * (`composeObjectStorageServiceEnvironment`): `objectstorage-endpoint-missing` a binding whose node
133
140
  * states no valid `corestoreObjectStorage`, `objectstorage-binding-invalid` a binding
134
141
  * `createObjectStorageBindingEnvironment` refuses, and `objectstorage-environment-conflict` a name
135
- * the service's own environment or another binding already sets.
142
+ * the service's own environment, one of its launcher-environment secrets or another binding already
143
+ * sets.
136
144
  *
137
145
  * `database-endpoint-missing` is an enabled database binding placed on a node that states no valid
138
146
  * `corestoreDatabase`: its credential cannot be addressed to any host a workload reaches, so it is
@@ -201,10 +201,17 @@ const findEnvironmentCollisions = (
201
201
  keysArg: readonly string[],
202
202
  ): string[] => keysArg.filter((keyArg) => claimedKeysArg.has(keyArg)).sort();
203
203
 
204
- const describeEnvironmentCollisions = (collisionsArg: readonly string[]): string => (
205
- `objectstorage binding environment collides with ${collisionsArg.join(', ')} already set on the `
206
- + 'workload'
207
- );
204
+ const describeEnvironmentCollisions = (
205
+ collisionsArg: readonly string[],
206
+ secretNamesArg: ReadonlySet<string> = new Set(),
207
+ ): string => {
208
+ const secretCollisions = collisionsArg.filter((keyArg) => secretNamesArg.has(keyArg));
209
+ return `objectstorage binding environment collides with ${collisionsArg.join(', ')} already set on `
210
+ + 'the workload' + (secretCollisions.length > 0
211
+ ? `; ${secretCollisions.join(', ')} reaches it as a launcher-environment secret, which would `
212
+ + 'replace the binding\'s value'
213
+ : '');
214
+ };
208
215
 
209
216
  /**
210
217
  * A workload environment with one binding's environment added.
@@ -252,8 +259,8 @@ export type TObjectStorageEnvironmentWaitingReason = Extract<
252
259
  * `corestoreObjectStorage`; `objectstorage-binding-invalid` a binding
253
260
  * `createObjectStorageBindingEnvironment()` refuses (not `objectstorage`, no canonical bucket, no
254
261
  * canonical retention intent, or evidence that does not prove this binding);
255
- * `objectstorage-environment-conflict` a name the service's own environment or another binding
256
- * already sets.
262
+ * `objectstorage-environment-conflict` a name the service's own environment, one of its
263
+ * launcher-environment secrets or another binding already sets.
257
264
  */
258
265
  export type TObjectStorageEnvironmentRefusal = Extract<
259
266
  TServiceRuntimeRefusal,
@@ -285,16 +292,27 @@ export type TObjectStorageServiceEnvironmentComposition =
285
292
  * already claims all twenty of its names, so a collision with it is refused now, not once its
286
293
  * evidence arrives. The first refusal is answered, otherwise the first wait, otherwise the
287
294
  * composed environment. The inputs are not changed.
295
+ *
296
+ * `secretEnvironmentNamesArg` names the variables the workload receives as launcher-environment
297
+ * secrets (each `ISecretResolvedLauncherEnvironmentDelivery.variableName` of its desired
298
+ * manifest), names only, never a value. The launcher sets a secret over a plain variable of the same name, so a
299
+ * secret named like one of a binding's names would silently replace the binding's value: such a
300
+ * name claims the binding's name exactly as the service's own environment does, and is refused as
301
+ * `objectstorage-environment-conflict`. A secret by any other name is not this composition's
302
+ * concern, and neither are a binding's credential names (`corestoreObjectStorageCredentialSecretKeys`),
303
+ * which the binding itself delivers as secrets and never as plain names.
288
304
  */
289
305
  export const composeObjectStorageServiceEnvironment = async (
290
306
  serviceEnvironmentArg: Readonly<Record<string, string>>,
291
307
  sourcesArg: readonly IObjectStorageBindingEnvironmentSource[],
308
+ secretEnvironmentNamesArg: readonly string[] = [],
292
309
  ): Promise<TObjectStorageServiceEnvironmentComposition> => {
293
310
  const sources = [...sourcesArg].sort((leftArg, rightArg) => (
294
311
  leftArg.binding.id < rightArg.binding.id ? -1 : leftArg.binding.id > rightArg.binding.id ? 1 : 0
295
312
  ));
296
313
  let environment: Record<string, string> = { ...serviceEnvironmentArg };
297
- const claimedKeys = new Set(Object.keys(environment));
314
+ const secretNames: ReadonlySet<string> = new Set(secretEnvironmentNamesArg);
315
+ const claimedKeys = new Set([...Object.keys(environment), ...secretNames]);
298
316
  let wait: Extract<TObjectStorageServiceEnvironmentComposition, { status: 'waiting' }> | null = null;
299
317
  for (const source of sources) {
300
318
  const bindingId = source.binding.id;
@@ -308,7 +326,7 @@ export const composeObjectStorageServiceEnvironment = async (
308
326
  const collisions = findEnvironmentCollisions(claimedKeys, keys);
309
327
  if (collisions.length > 0) {
310
328
  return { status: 'refused', reason: 'objectstorage-environment-conflict', bindingId,
311
- message: describeEnvironmentCollisions(collisions) };
329
+ message: describeEnvironmentCollisions(collisions, secretNames) };
312
330
  }
313
331
  for (const key of keys) claimedKeys.add(key);
314
332
  if (resolution.status === 'composed') {
@@ -8,6 +8,9 @@ import type {
8
8
  IDeploymentMachineUserMetadata, IDeploymentMachineUserMutationResult,
9
9
  TDeploymentMachineUserMutation,
10
10
  } from '../data/deploymentmachineuser.js';
11
+ import type {
12
+ IDelegatedAdminTokenMetadata, IDelegatedAdminTokenMint, IDelegatedAdminTokenMintResult,
13
+ } from '../data/delegatedadmintoken.js';
11
14
 
12
15
  export interface IReq_Admin_GetServiceMachineCredential extends plugins.typedrequestInterfaces.implementsTR<
13
16
  plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredential
@@ -198,3 +201,51 @@ export interface IReq_Cloudly_Coreflow_ReconcileBaseServices extends plugins.typ
198
201
  routingProof?: ICoreflowRoutingProofResult;
199
202
  };
200
203
  }
204
+
205
+ /**
206
+ * Mints one delegated admin token (`delegatedAdminTokenContract`) for what the request states
207
+ * (`IDelegatedAdminTokenMint`: its scopes, optional `lifetimeMs` and `secretKeyPrefixes`). Requires
208
+ * a human administrator who signed in within five minutes; a delegated session is refused. The
209
+ * plaintext token is answered on this answer only and is never retrievable again, so the request is
210
+ * sent once and its answer goes past every traffic hook. `lifetimeMs` absent is
211
+ * `defaultLifetimeMs`; Cloudly refuses any lifetime above `maximumLifetimeMs`. Refusals travel as
212
+ * `IDelegatedAdminTokenErrorData`.
213
+ */
214
+ export interface IReq_Admin_MintDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
215
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MintDelegatedAdminToken
216
+ > {
217
+ method: 'mintDelegatedAdminToken';
218
+ request: { identity: IIdentityCredential } & IDelegatedAdminTokenMint;
219
+ response: IDelegatedAdminTokenMintResult;
220
+ }
221
+
222
+ /**
223
+ * Lists the active delegated admin tokens, value-free, newest first: not expired, not revoked,
224
+ * and their administrator still a human administrator at the credential generation each was
225
+ * minted under. At most `delegatedAdminTokenContract.maximumListedTokens` are answered;
226
+ * `truncated` is true exactly when active tokens beyond them were left out, so no token that
227
+ * needs revoking is hidden without notice. Requires an administrator; a delegated session is
228
+ * refused. Refusals travel as `IDelegatedAdminTokenErrorData`.
229
+ */
230
+ export interface IReq_Admin_GetDelegatedAdminTokens extends plugins.typedrequestInterfaces.implementsTR<
231
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetDelegatedAdminTokens
232
+ > {
233
+ method: 'getDelegatedAdminTokens';
234
+ request: { identity: IIdentityCredential };
235
+ response: { tokens: IDelegatedAdminTokenMetadata[]; truncated: boolean };
236
+ }
237
+
238
+ /**
239
+ * Revokes one active delegated admin token; once the revocation commits, no use of the token
240
+ * commits after it, and every session exchanged for it is refused. A success has committed.
241
+ * Requires an administrator, the minting one or any other; a delegated session is refused.
242
+ * Refusals travel as `IDelegatedAdminTokenErrorData`: a token that already does not act (revoked,
243
+ * expired, or its administrator's credential or role changed) is `DELEGATED_ADMIN_TOKEN_INACTIVE`.
244
+ */
245
+ export interface IReq_Admin_RevokeDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
246
+ plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_RevokeDelegatedAdminToken
247
+ > {
248
+ method: 'revokeDelegatedAdminToken';
249
+ request: { identity: IIdentityCredential; tokenId: string };
250
+ response: { metadata: IDelegatedAdminTokenMetadata };
251
+ }
@@ -12,7 +12,7 @@ export interface IReq_GetRegistryById extends plugins.typedrequestInterfaces.imp
12
12
  id: string;
13
13
  };
14
14
  response: {
15
- registry: data.IExternalRegistry;
15
+ registry: data.IExternalRegistryPublic;
16
16
  };
17
17
  }
18
18
 
@@ -25,7 +25,7 @@ export interface IReq_GetRegistries extends plugins.typedrequestInterfaces.imple
25
25
  identity: userInterfaces.IIdentity;
26
26
  };
27
27
  response: {
28
- registries: data.IExternalRegistry[];
28
+ registries: data.IExternalRegistryPublic[];
29
29
  };
30
30
  }
31
31
 
@@ -39,7 +39,7 @@ export interface IReq_CreateRegistry extends plugins.typedrequestInterfaces.impl
39
39
  registryData: data.IExternalRegistry['data'];
40
40
  };
41
41
  response: {
42
- registry: data.IExternalRegistry;
42
+ registry: data.IExternalRegistryPublic;
43
43
  };
44
44
  }
45
45
 
@@ -51,10 +51,14 @@ export interface IReq_UpdateRegistry extends plugins.typedrequestInterfaces.impl
51
51
  request: {
52
52
  identity: userInterfaces.IIdentity;
53
53
  registryId: string;
54
- registryData: data.IExternalRegistry['data'];
54
+ /**
55
+ * The operator fields to change; a field left out keeps its stored value. `password` is
56
+ * write-only: missing or empty keeps the stored credential, and `authType: 'none'` removes it.
57
+ */
58
+ registryData: Partial<data.TExternalRegistryOperatorData>;
55
59
  };
56
60
  response: {
57
- resultRegistry: data.IExternalRegistry;
61
+ resultRegistry: data.IExternalRegistryPublic;
58
62
  };
59
63
  }
60
64
 
@@ -82,8 +86,10 @@ export interface IReq_VerifyRegistry extends plugins.typedrequestInterfaces.impl
82
86
  registryId: string;
83
87
  };
84
88
  response: {
89
+ /** True exactly for the `authenticated` and `anonymous` outcomes. */
85
90
  success: boolean;
91
+ outcome: data.TExternalRegistryVerifyOutcome;
86
92
  message?: string;
87
- registry?: data.IExternalRegistry;
93
+ registry: data.IExternalRegistryPublic;
88
94
  };
89
95
  }
@@ -7,6 +7,10 @@ import * as userInterfaces from '../data/user.js'
7
7
 
8
8
  /**
9
9
  * get the identity that then will be used to get the config
10
+ *
11
+ * A delegated admin token (`delegatedAdminTokenContract`) is exchanged here too. Its identity is
12
+ * `type: 'machine'`, `role: 'api'`, `userId: 'delegated-admin-token:<tokenId>'`, expiring with the
13
+ * token; only the requests of the token's scopes admit it, and each re-reads the token.
10
14
  */
11
15
  export interface IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken
12
16
  extends plugins.typedrequestInterfaces.implementsTR<
@@ -455,7 +455,22 @@ extends plugins.typedrequestInterfaces.implementsTR<
455
455
  request: {
456
456
  identity: IIdentity;
457
457
  serviceId: string;
458
- serviceData: TServiceUpdateWritableData;
458
+ /**
459
+ * The fields to change, merged over the stored service one key deep: a key the update omits
460
+ * keeps its stored value, and a key it states replaces the stored value whole (a stated
461
+ * `ports`, `mail` or `domains` is the complete new value, not a patch of the old one).
462
+ * `containerArgs: null` removes the stored argument vector; no other field takes `null`.
463
+ * `registryTarget` is the exception to "replaces": Cloudly derives it from the service and
464
+ * its image version, so a stated one only makes Cloudly derive it again and is never stored.
465
+ * An optional field cannot be cleared yet: a key whose value is `undefined` is dropped in
466
+ * transit, so it reads as omitted (explicit `null` removal for optional fields is a follow-up).
467
+ *
468
+ * Cloudly's guards ask which keys an update states, so send only the fields that change: a
469
+ * service with a runtime spec refuses any update stating `placement` or `scaleFactor`, and an
470
+ * immutable service refuses `imageId`, `imageVersion`, `registryTarget`, `scaleFactor` and
471
+ * `placement`, even when the stated value repeats the stored one.
472
+ */
473
+ serviceData: Partial<TServiceUpdateWritableData>;
459
474
  };
460
475
  response: {
461
476
  service: IService;