@serve.zone/interfaces 32.66.0 → 32.68.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.
@@ -5,6 +5,23 @@ export const hostedAppOidcAppInstanceIdClaim = 'servezone_app_instance_id' as co
5
5
  export const hostedAppMachineServiceIdClaim = 'servezone_service_id' as const;
6
6
  export const hostedAppMachineAudience = 'serve.zone/hosted-app-lifecycle' as const;
7
7
 
8
+ /**
9
+ * The environment variable names under which a host (Onebox, Cloudly) hands a hosted app its
10
+ * runtime identity. `appInstanceId`, `hostType` and `runtimeUrl` travel in the public environment;
11
+ * `controlToken` is a secret and carries the host's machine JWT (`IHostedAppMachineClaims`,
12
+ * audience `hostedAppMachineAudience`). The app sends that JWT verbatim as `identity.jwt` of the
13
+ * hosted lifecycle requests and never logs, stores or decodes it. The host rotates it only by
14
+ * replacing the workload and accepts only the token it issued last.
15
+ *
16
+ * These are shipped values that running workloads read: never rename them.
17
+ */
18
+ export const hostedAppRuntimeIdentityKeys = {
19
+ appInstanceId: 'SERVEZONE_APP_INSTANCE_ID',
20
+ hostType: 'SERVEZONE_APP_HOST_TYPE',
21
+ runtimeUrl: 'SERVEZONE_RUNTIME_URL',
22
+ controlToken: 'SERVEZONE_APP_CONTROL_TOKEN',
23
+ } as const;
24
+
8
25
  export interface IHostedAppMachineClaims {
9
26
  sub: string;
10
27
  aud: typeof hostedAppMachineAudience;
@@ -206,9 +206,24 @@ export interface IImageReleaseLegacyImportEvidence extends IImageReleaseTrustedE
206
206
  sourceRootDigest: TSha256Digest;
207
207
  }
208
208
 
209
+ /**
210
+ * Cloudly's own copy of one of the service's accepted releases under a release redeploy's tag.
211
+ *
212
+ * Nothing was built or pushed: the release's root was tagged again in the same repository, so
213
+ * `sourceRootDigest` equals `registryRootDigest`, and `sourceReleaseId` names the release row the
214
+ * root belongs to. `sourceTag` is the tag the release was accepted under before.
215
+ */
216
+ export interface IImageReleaseRedeployEvidence extends IImageReleaseTrustedEvidenceBase {
217
+ source: 'cloudly-release-redeploy';
218
+ sourceReleaseId: string;
219
+ sourceTag: string;
220
+ sourceRootDigest: TSha256Digest;
221
+ }
222
+
209
223
  export type IImageReleaseTrustedEvidence =
210
224
  | IImageReleaseRegistryPushEvidence
211
- | IImageReleaseLegacyImportEvidence;
225
+ | IImageReleaseLegacyImportEvidence
226
+ | IImageReleaseRedeployEvidence;
212
227
 
213
228
  export const selectTrustedImageReleaseEvidence = (argsArg: {
214
229
  release: IImageRelease;
@@ -222,8 +237,9 @@ export const selectTrustedImageReleaseEvidence = (argsArg: {
222
237
  digest: TSha256Digest;
223
238
  /**
224
239
  * The one evidence source the operation's image may have come from: a legacy-import operation
225
- * accepts only Cloudly's import, every other operation only its actor's registry push. Omitted,
226
- * it is the registry push, the only source an operation without a legacy import has.
240
+ * accepts only Cloudly's import, a release-redeploy operation only Cloudly's redeploy copy, every
241
+ * other operation only its actor's registry push. Omitted, it is the registry push, the only
242
+ * source an operation without a legacy import or a release redeploy has.
227
243
  */
228
244
  evidenceSource?: IImageReleaseTrustedEvidence['source'];
229
245
  }): { evidence?: IImageReleaseTrustedEvidence; errors: string[] } => {
@@ -248,6 +264,13 @@ export const selectTrustedImageReleaseEvidence = (argsArg: {
248
264
  evidenceArg.sourceRootDigest,
249
265
  ]
250
266
  : []),
267
+ ...(evidenceArg.source === 'cloudly-release-redeploy'
268
+ ? [
269
+ evidenceArg.sourceReleaseId,
270
+ evidenceArg.sourceTag,
271
+ evidenceArg.sourceRootDigest,
272
+ ]
273
+ : []),
251
274
  ].join('\u0000');
252
275
  const uniqueEvidence = new Map<string, IImageReleaseTrustedEvidence>();
253
276
  const errors: string[] = [];
@@ -284,6 +307,16 @@ export const selectTrustedImageReleaseEvidence = (argsArg: {
284
307
  errors.push('trusted release evidence is ambiguous');
285
308
  }
286
309
  const evidence = evidenceMatches.length === 1 ? evidenceMatches[0] : undefined;
310
+ // A redeploy copies one root of this release row under a new tag; it never makes a new root,
311
+ // and only a row that was accepted before (promoted successfully) can be redeployed.
312
+ if (evidence?.source === 'cloudly-release-redeploy'
313
+ && (argsArg.release.data.acceptedAt === undefined
314
+ || evidence.sourceRootDigest !== argsArg.release.data.registryRootDigest
315
+ || evidence.sourceReleaseId !== argsArg.release.id
316
+ || evidence.sourceTag === evidence.tag
317
+ || !argsArg.release.data.observedTags.includes(evidence.sourceTag))) {
318
+ errors.push('release redeploy evidence must name this accepted release, its root and an earlier tag of it');
319
+ }
287
320
  if (evidence && evidence.manifestMediaType !== argsArg.release.data.manifestMediaType) {
288
321
  errors.push('trusted release and release root media types disagree');
289
322
  }
package/ts/data/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from './deploymentlegacyimport.js';
18
18
  export * from './deploymentmachineuser.js';
19
19
  export * from './deploymentoperation.js';
20
20
  export * from './deploymentpreflight.js';
21
+ export * from './deploymentreleaseredeploy.js';
21
22
  export * from './migration.js';
22
23
  export * from './dns.js';
23
24
  export * from './docker.js';
package/ts/data/user.ts CHANGED
@@ -22,7 +22,13 @@ export type TServiceDeploymentCapability =
22
22
  | 'deployment:rollback-image'
23
23
  | 'deployment:rollback-route'
24
24
  | 'deployment:retry'
25
- | 'deployment:cleanup';
25
+ | 'deployment:cleanup'
26
+ /**
27
+ * Start a release redeploy of the service (`startServiceReleaseDeployment`): Cloudly deploys one
28
+ * of the service's own accepted, retained releases again and runs the operation itself. Granted
29
+ * on an existing-service grant only; administrators hold it implicitly.
30
+ */
31
+ | 'deployment:redeploy-release';
26
32
 
27
33
  /**
28
34
  * Server-persisted authority for one logical service. These grants are never
@@ -64,6 +70,7 @@ const serviceDeploymentCapabilities = new Set<TServiceDeploymentCapability>([
64
70
  'deployment:rollback-route',
65
71
  'deployment:retry',
66
72
  'deployment:cleanup',
73
+ 'deployment:redeploy-release',
67
74
  ]);
68
75
 
69
76
  const deploymentBoundaryIdentifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
@@ -112,6 +119,10 @@ export const validateServiceDeploymentGrant = (
112
119
  && capabilities.includes('deployment:adopt-existing')) {
113
120
  errors.push('deployment:adopt-existing requires an existing-service grant');
114
121
  }
122
+ if (capabilities && grant.scope !== 'service'
123
+ && capabilities.includes('deployment:redeploy-release')) {
124
+ errors.push('deployment:redeploy-release requires an existing-service grant');
125
+ }
115
126
  if (capabilities && grant.scope !== 'organization-service-slot'
116
127
  && capabilities.includes('deployment:import-legacy')) {
117
128
  errors.push('deployment:import-legacy requires an organization-service-slot grant');
@@ -75,6 +75,11 @@ extends plugins.typedrequestInterfaces.implementsTR<
75
75
  };
76
76
  }
77
77
 
78
+ /**
79
+ * @deprecated Stores a deployment record and deploys nothing; no runtime writes these records.
80
+ * A deployment is a release redeploy (`startServiceReleaseDeployment`) or a tsdeploy deployment.
81
+ * It is removed in the next Cloudly major.
82
+ */
78
83
  export interface IReq_Any_Cloudly_CreateDeployment
79
84
  extends plugins.typedrequestInterfaces.implementsTR<
80
85
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -17,6 +17,10 @@ import type {
17
17
  IServiceDeploymentStatus,
18
18
  } from '../data/deploymentoperation.js';
19
19
  import type { TSha256Digest } from '../data/immutableimage.js';
20
+ import type {
21
+ IServiceDeploymentOperationHistoryQuery,
22
+ IServiceReleaseDeploymentStartInput,
23
+ } from '../data/deploymentreleaseredeploy.js';
20
24
  import type {
21
25
  ILegacyServiceHostObservation,
22
26
  ILegacyServiceRuntimeAttestation,
@@ -214,6 +218,11 @@ extends plugins.typedrequestInterfaces.implementsTR<
214
218
  };
215
219
  }
216
220
 
221
+ /**
222
+ * @deprecated Rolls back by re-opening an earlier operation; nothing calls it and it is not
223
+ * maintained. A rollback is a release redeploy (`startServiceReleaseDeployment`) of the earlier
224
+ * release. It is removed in the next Cloudly major.
225
+ */
217
226
  export interface IRequest_Any_Cloudly_RollbackServiceImageRelease
218
227
  extends plugins.typedrequestInterfaces.implementsTR<
219
228
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -262,6 +271,52 @@ extends plugins.typedrequestInterfaces.implementsTR<
262
271
  };
263
272
  }
264
273
 
274
+ /**
275
+ * Starts a release redeploy: Cloudly deploys one of the service's own accepted, retained and whole
276
+ * releases again (`data.deploymentreleaseredeploy`) and runs the operation itself to `succeeded` or
277
+ * `failed`. The answer is the operation as it stands once it was started; its state, failure and
278
+ * evidence are read with `readServiceDeploymentStatus`, never assumed from the answer.
279
+ *
280
+ * An administrator or a deployment machine user holding `deployment:redeploy-release` on the
281
+ * service may start one; the operation's actor is the caller. A refusal carries one of
282
+ * `data.serviceReleaseDeploymentRefusalCodes` or a shared deployment code in its `code`. The same
283
+ * `idempotencyKey` with the same request answers the operation it started.
284
+ */
285
+ export interface IRequest_Any_Cloudly_StartServiceReleaseDeployment
286
+ extends plugins.typedrequestInterfaces.implementsTR<
287
+ plugins.typedrequestInterfaces.ITypedRequest,
288
+ IRequest_Any_Cloudly_StartServiceReleaseDeployment
289
+ > {
290
+ method: 'startServiceReleaseDeployment';
291
+ request: {
292
+ identity: IIdentityCredential;
293
+ } & IServiceReleaseDeploymentStartInput;
294
+ response: {
295
+ operation: IServiceDeploymentOperation;
296
+ };
297
+ }
298
+
299
+ /**
300
+ * One page of a service's deployment operations, newest first: every operation of every origin
301
+ * (tsdeploy's and Cloudly's own). `nextBefore` names the operation the next page starts after and
302
+ * is absent on the last page. Administrators, or a deployment machine user holding
303
+ * `deployment:status` on the service.
304
+ */
305
+ export interface IRequest_Any_Cloudly_GetServiceDeploymentOperations
306
+ extends plugins.typedrequestInterfaces.implementsTR<
307
+ plugins.typedrequestInterfaces.ITypedRequest,
308
+ IRequest_Any_Cloudly_GetServiceDeploymentOperations
309
+ > {
310
+ method: 'getServiceDeploymentOperations';
311
+ request: {
312
+ identity: IIdentityCredential;
313
+ } & IServiceDeploymentOperationHistoryQuery;
314
+ response: {
315
+ operations: IServiceDeploymentOperation[];
316
+ nextBefore?: string;
317
+ };
318
+ }
319
+
265
320
  /**
266
321
  * Answers one deployment operation's status and advances it: a rolling-out operation whose runtime
267
322
  * evidence proves success or failure is transitioned before the answer. A deployment that polls