@serve.zone/interfaces 9.0.0 → 10.1.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 +21 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/data/backup.d.ts +229 -0
- package/dist_ts/data/user.d.ts +4 -3
- package/dist_ts/requests/backup.d.ts +148 -1
- package/dist_ts/requests/config.d.ts +1 -1
- package/package.json +3 -3
- package/readme.md +3 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/backup.ts +308 -0
- package/ts/data/user.ts +6 -4
- package/ts/requests/backup.ts +190 -0
- package/ts/requests/config.ts +1 -1
package/changelog.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-07-13 - 10.1.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- define isolated restore orchestration contracts (backup)
|
|
8
|
+
- Add non-runnable scratch namespaces with backup-derived cluster and source authority.
|
|
9
|
+
- Add idempotency keys, revision compare-and-swap, exact cluster/node routing, verification, cursor pagination, durable backup holds, and retryable per-resource cleanup progress.
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
|
|
13
|
+
- Remove the legacy implicit-any build suppression and refresh the age-eligible TypeScript toolchain.
|
|
14
|
+
|
|
15
|
+
## 2026-07-10 - 10.0.0
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
- accept JWT-only credentials for cluster config requests (config)
|
|
20
|
+
- Add IIdentityCredential as a JWT-only authentication contract.
|
|
21
|
+
- Make IIdentity extend IIdentityCredential so full identities remain compatible where credentials are accepted.
|
|
22
|
+
- Update getClusterConfig request identity to require only the JWT needed for server-side resolution.
|
|
23
|
+
|
|
3
24
|
## 2026-07-10 - 9.0.0
|
|
4
25
|
|
|
5
26
|
### Breaking Changes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '
|
|
6
|
+
version: '10.1.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
package/dist_ts/data/backup.d.ts
CHANGED
|
@@ -53,6 +53,19 @@ export interface IBackupRestoreEvent {
|
|
|
53
53
|
status: 'restored' | 'failed';
|
|
54
54
|
errorText?: string;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Durable retention hold owned by one isolated restore. An entry is active
|
|
58
|
+
* while `releasedAt` is absent. Active holds never expire implicitly:
|
|
59
|
+
* retention may neither claim nor delete the backup until every active hold
|
|
60
|
+
* has been released after successful scratch-resource and staging cleanup.
|
|
61
|
+
*/
|
|
62
|
+
export interface IBackupRestoreHold {
|
|
63
|
+
restoreId: string;
|
|
64
|
+
idempotencyKey: string;
|
|
65
|
+
acquiredAt: number;
|
|
66
|
+
releasedAt?: number;
|
|
67
|
+
releaseReason?: string;
|
|
68
|
+
}
|
|
56
69
|
export interface IBackupRecord {
|
|
57
70
|
id: string;
|
|
58
71
|
serviceId: string;
|
|
@@ -76,6 +89,12 @@ export interface IBackupRecord {
|
|
|
76
89
|
requestedBy?: string;
|
|
77
90
|
errorText?: string;
|
|
78
91
|
restoreHistory?: IBackupRestoreEvent[];
|
|
92
|
+
/**
|
|
93
|
+
* Authoritative isolated-restore holds. Cloudly atomically appends the
|
|
94
|
+
* active entry with restore creation. Failed archive cleanup must retain
|
|
95
|
+
* this backup record so hold recovery remains possible.
|
|
96
|
+
*/
|
|
97
|
+
restoreHolds?: IBackupRestoreHold[];
|
|
79
98
|
tags?: Record<string, string>;
|
|
80
99
|
}
|
|
81
100
|
/** Retention policy understood by containerarchive prune. */
|
|
@@ -92,3 +111,213 @@ export interface IArchivePruneResult {
|
|
|
92
111
|
freedBytes: number;
|
|
93
112
|
dryRun: boolean;
|
|
94
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* JWT-free actor projection persisted with isolated-restore audit data.
|
|
116
|
+
* Authentication credentials must never be copied into restore records.
|
|
117
|
+
*/
|
|
118
|
+
export interface IIsolatedRestoreActor {
|
|
119
|
+
userId: string;
|
|
120
|
+
type: 'human' | 'machine' | 'system';
|
|
121
|
+
role?: 'admin' | 'user' | 'api' | 'cluster';
|
|
122
|
+
}
|
|
123
|
+
export type TIsolatedRestoreStatus = 'pending' | 'preparing' | 'staging' | 'restoring' | 'verifying' | 'ready' | 'failed' | 'cleanup-pending' | 'cleaning' | 'cleaned' | 'cleanup-failed';
|
|
124
|
+
export type TIsolatedRestoreErrorPhase = 'validation' | 'source-backup-hold' | 'preparation' | 'staging' | 'restoring' | 'verifying' | 'cleanup';
|
|
125
|
+
/** Append-only structured failure entry for one isolated restore. */
|
|
126
|
+
export interface IIsolatedRestoreError {
|
|
127
|
+
sequence: number;
|
|
128
|
+
phase: TIsolatedRestoreErrorPhase;
|
|
129
|
+
code: string;
|
|
130
|
+
message: string;
|
|
131
|
+
retryable: boolean;
|
|
132
|
+
occurredAt: number;
|
|
133
|
+
}
|
|
134
|
+
export type TIsolatedRestoreAuditAction = 'created' | 'status-transitioned' | 'source-backup-hold-acquired' | 'cleanup-requested' | 'expired' | 'source-backup-hold-release-requested' | 'source-backup-hold-released';
|
|
135
|
+
/** Append-only lifecycle event. `sequence` is strictly increasing per restore. */
|
|
136
|
+
export interface IIsolatedRestoreAuditEvent {
|
|
137
|
+
sequence: number;
|
|
138
|
+
action: TIsolatedRestoreAuditAction;
|
|
139
|
+
actor: IIsolatedRestoreActor;
|
|
140
|
+
occurredAt: number;
|
|
141
|
+
fromStatus?: TIsolatedRestoreStatus;
|
|
142
|
+
toStatus?: TIsolatedRestoreStatus;
|
|
143
|
+
reason?: string;
|
|
144
|
+
}
|
|
145
|
+
export interface IIsolatedRestoreVolumeMapping {
|
|
146
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
147
|
+
id: string;
|
|
148
|
+
type: 'volume';
|
|
149
|
+
source: {
|
|
150
|
+
snapshotId: string;
|
|
151
|
+
volumeName: string;
|
|
152
|
+
mountPath: string;
|
|
153
|
+
};
|
|
154
|
+
target: {
|
|
155
|
+
volumeName: string;
|
|
156
|
+
mountPath: string;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export interface IIsolatedRestoreDatabaseMapping {
|
|
160
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
161
|
+
id: string;
|
|
162
|
+
type: 'database';
|
|
163
|
+
source: {
|
|
164
|
+
snapshotId: string;
|
|
165
|
+
resourceName: string;
|
|
166
|
+
databaseName: string;
|
|
167
|
+
};
|
|
168
|
+
target: {
|
|
169
|
+
resourceName: string;
|
|
170
|
+
databaseName: string;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export interface IIsolatedRestoreObjectStorageMapping {
|
|
174
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
175
|
+
id: string;
|
|
176
|
+
type: 'objectstorage';
|
|
177
|
+
source: {
|
|
178
|
+
snapshotId: string;
|
|
179
|
+
resourceName: string;
|
|
180
|
+
bucketName: string;
|
|
181
|
+
};
|
|
182
|
+
target: {
|
|
183
|
+
resourceName: string;
|
|
184
|
+
bucketName: string;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export type TIsolatedRestoreResourceMapping = IIsolatedRestoreVolumeMapping | IIsolatedRestoreDatabaseMapping | IIsolatedRestoreObjectStorageMapping;
|
|
188
|
+
export type TIsolatedRestoreVerificationStatus = 'pending' | 'running' | 'passed' | 'failed';
|
|
189
|
+
export interface IIsolatedRestoreVerificationCheck {
|
|
190
|
+
/** Stable machine-readable check code. */
|
|
191
|
+
code: string;
|
|
192
|
+
status: 'passed' | 'failed';
|
|
193
|
+
message?: string;
|
|
194
|
+
}
|
|
195
|
+
export interface IIsolatedRestoreResourceVerification {
|
|
196
|
+
resourceMappingId: string;
|
|
197
|
+
resourceType: TBackupResourceType;
|
|
198
|
+
status: TIsolatedRestoreVerificationStatus;
|
|
199
|
+
checks: IIsolatedRestoreVerificationCheck[];
|
|
200
|
+
checkedAt?: number;
|
|
201
|
+
}
|
|
202
|
+
/** Complete verification state returned by the target data plane. */
|
|
203
|
+
export interface IIsolatedRestoreVerification {
|
|
204
|
+
status: TIsolatedRestoreVerificationStatus;
|
|
205
|
+
results: IIsolatedRestoreResourceVerification[];
|
|
206
|
+
startedAt?: number;
|
|
207
|
+
completedAt?: number;
|
|
208
|
+
}
|
|
209
|
+
export type TIsolatedRestoreCleanupTrigger = 'manual' | 'expired' | 'restore-failed' | 'policy';
|
|
210
|
+
export type TIsolatedRestoreCleanupProgressStatus = 'pending' | 'running' | 'succeeded' | 'failed';
|
|
211
|
+
export interface IIsolatedRestoreResourceCleanupProgress {
|
|
212
|
+
resourceMappingId: string;
|
|
213
|
+
resourceType: TBackupResourceType;
|
|
214
|
+
status: TIsolatedRestoreCleanupProgressStatus;
|
|
215
|
+
attempts: number;
|
|
216
|
+
updatedAt?: number;
|
|
217
|
+
errorCode?: string;
|
|
218
|
+
errorMessage?: string;
|
|
219
|
+
}
|
|
220
|
+
export interface IIsolatedRestoreStagingCleanupProgress {
|
|
221
|
+
stagingArchiveId: string;
|
|
222
|
+
status: TIsolatedRestoreCleanupProgressStatus;
|
|
223
|
+
attempts: number;
|
|
224
|
+
updatedAt?: number;
|
|
225
|
+
errorCode?: string;
|
|
226
|
+
errorMessage?: string;
|
|
227
|
+
}
|
|
228
|
+
/** Data-plane cleanup progress shared by public and internal status views. */
|
|
229
|
+
export interface IIsolatedRestoreCleanupProgress {
|
|
230
|
+
resources: IIsolatedRestoreResourceCleanupProgress[];
|
|
231
|
+
stagingArchive: IIsolatedRestoreStagingCleanupProgress;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Cleanup metadata. The source-backup hold remains active until every
|
|
235
|
+
* resource entry and the staging entry report `succeeded`. `cleanup-failed`
|
|
236
|
+
* is retryable and must not release the hold.
|
|
237
|
+
*/
|
|
238
|
+
export interface IIsolatedRestoreCleanup extends IIsolatedRestoreCleanupProgress {
|
|
239
|
+
trigger: TIsolatedRestoreCleanupTrigger;
|
|
240
|
+
reason: string;
|
|
241
|
+
requestedBy: IIsolatedRestoreActor;
|
|
242
|
+
requestedAt: number;
|
|
243
|
+
startedAt?: number;
|
|
244
|
+
completedAt?: number;
|
|
245
|
+
}
|
|
246
|
+
export type TIsolatedRestoreSourceBackupHoldStatus = 'acquired' | 'release-pending' | 'released';
|
|
247
|
+
/**
|
|
248
|
+
* Restore-side projection of the authoritative IBackupRecord hold. The hold
|
|
249
|
+
* is non-expiring and may enter `released` only after exact cleanup success.
|
|
250
|
+
*/
|
|
251
|
+
export interface IIsolatedRestoreSourceBackupHold {
|
|
252
|
+
status: TIsolatedRestoreSourceBackupHoldStatus;
|
|
253
|
+
acquiredAt: number;
|
|
254
|
+
releaseRequestedAt?: number;
|
|
255
|
+
releasedAt?: number;
|
|
256
|
+
releaseReason?: string;
|
|
257
|
+
}
|
|
258
|
+
export type TIsolatedRestoreNodeStatus = 'preparing' | 'prepared' | 'staging' | 'restoring' | 'verifying' | 'ready' | 'failed' | 'cleaning' | 'cleaned' | 'cleanup-failed';
|
|
259
|
+
/**
|
|
260
|
+
* Data-plane state for one exact cluster/node/restore routing key. `revision`
|
|
261
|
+
* advances after every mutation and is the compare-and-swap value used by
|
|
262
|
+
* prepare, execute, and cleanup requests.
|
|
263
|
+
*/
|
|
264
|
+
export interface IIsolatedRestoreNodeState {
|
|
265
|
+
restoreId: string;
|
|
266
|
+
clusterId: string;
|
|
267
|
+
targetNodeName: string;
|
|
268
|
+
sourceBackupId: string;
|
|
269
|
+
sourceServiceId: string;
|
|
270
|
+
scratchNamespaceId: string;
|
|
271
|
+
stagingArchiveId: string;
|
|
272
|
+
revision: number;
|
|
273
|
+
status: TIsolatedRestoreNodeStatus;
|
|
274
|
+
completedResourceMappingIds: string[];
|
|
275
|
+
verification: IIsolatedRestoreVerification;
|
|
276
|
+
cleanup?: IIsolatedRestoreCleanupProgress;
|
|
277
|
+
preparedAt?: number;
|
|
278
|
+
readyAt?: number;
|
|
279
|
+
updatedAt: number;
|
|
280
|
+
lastErrorCode?: string;
|
|
281
|
+
lastErrorMessage?: string;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Durable control-plane record for a restore into a generated data-plane
|
|
285
|
+
* scratch namespace. The namespace is not an IService and carries no image,
|
|
286
|
+
* runtime, route, port, environment, or secret authority.
|
|
287
|
+
*
|
|
288
|
+
* `clusterId` and `sourceServiceId` are copied from the authoritative source
|
|
289
|
+
* backup, never accepted from the caller. `revision` advances monotonically
|
|
290
|
+
* for optimistic lifecycle updates. A restore execution failure (`failed`)
|
|
291
|
+
* must proceed through cleanup before its source-backup hold can be released.
|
|
292
|
+
*/
|
|
293
|
+
export interface IIsolatedRestoreRecord {
|
|
294
|
+
/** Server-generated restore id. */
|
|
295
|
+
id: string;
|
|
296
|
+
/** Caller key used to return the same restore after retried creation. */
|
|
297
|
+
idempotencyKey: string;
|
|
298
|
+
revision: number;
|
|
299
|
+
sourceBackupId: string;
|
|
300
|
+
sourceServiceId: string;
|
|
301
|
+
sourceServiceName?: string;
|
|
302
|
+
clusterId: string;
|
|
303
|
+
/** Server-generated, non-runnable data-plane namespace. */
|
|
304
|
+
scratchNamespaceId: string;
|
|
305
|
+
/** Server-generated archive namespace, distinct from sourceBackupId. */
|
|
306
|
+
stagingArchiveId: string;
|
|
307
|
+
targetNodeName: string;
|
|
308
|
+
purpose: string;
|
|
309
|
+
requestedBy: IIsolatedRestoreActor;
|
|
310
|
+
status: TIsolatedRestoreStatus;
|
|
311
|
+
resourceMappings: TIsolatedRestoreResourceMapping[];
|
|
312
|
+
verification: IIsolatedRestoreVerification;
|
|
313
|
+
sourceBackupHold: IIsolatedRestoreSourceBackupHold;
|
|
314
|
+
createdAt: number;
|
|
315
|
+
updatedAt: number;
|
|
316
|
+
expiresAt: number;
|
|
317
|
+
readyAt?: number;
|
|
318
|
+
cleanup?: IIsolatedRestoreCleanup;
|
|
319
|
+
/** Append-only; entries are never replaced when a later phase fails. */
|
|
320
|
+
errors: IIsolatedRestoreError[];
|
|
321
|
+
/** Append-only lifecycle audit trail. */
|
|
322
|
+
auditEvents: IIsolatedRestoreAuditEvent[];
|
|
323
|
+
}
|
package/dist_ts/data/user.d.ts
CHANGED
|
@@ -3,18 +3,19 @@ export interface IToken {
|
|
|
3
3
|
expiresAt: number;
|
|
4
4
|
assignedRoles: string[];
|
|
5
5
|
}
|
|
6
|
+
export interface IIdentityCredential {
|
|
7
|
+
jwt: string;
|
|
8
|
+
}
|
|
6
9
|
/**
|
|
7
10
|
* an identity is assumed by authentication as a user
|
|
8
11
|
* an identity is ephemeral and has to be renewed regularly
|
|
9
12
|
*/
|
|
10
|
-
export interface IIdentity {
|
|
13
|
+
export interface IIdentity extends IIdentityCredential {
|
|
11
14
|
name: string;
|
|
12
15
|
userId: string;
|
|
13
16
|
type: 'machine' | 'human';
|
|
14
17
|
role: 'admin' | 'user' | 'api' | 'cluster';
|
|
15
18
|
expiresAt: number;
|
|
16
|
-
/** the jwt token should contain above data for verification */
|
|
17
|
-
jwt: string;
|
|
18
19
|
}
|
|
19
20
|
export interface IUser {
|
|
20
21
|
id: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import type { IArchivePruneResult, IArchiveRetentionPolicy, IBackupArchiveManifest, IBackupArchiveObject, IBackupRecord, IBackupReplicationResult, TBackupSnapshot } from '../data/backup.js';
|
|
2
|
+
import type { IArchivePruneResult, IArchiveRetentionPolicy, IBackupArchiveManifest, IBackupArchiveObject, IBackupRecord, IBackupReplicationResult, IIsolatedRestoreNodeState, IIsolatedRestoreRecord, TBackupSnapshot, TIsolatedRestoreResourceMapping } from '../data/backup.js';
|
|
3
3
|
import type { IService } from '../data/service.js';
|
|
4
4
|
import type { IIdentity } from '../data/user.js';
|
|
5
5
|
export interface IReq_Any_Cloudly_CreateServiceBackup extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_CreateServiceBackup> {
|
|
@@ -146,3 +146,150 @@ export interface IReq_Cloudly_Coreflow_PruneNodeArchive extends plugins.typedreq
|
|
|
146
146
|
result?: IArchivePruneResult;
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates a restore in a new non-runnable data-plane scratch namespace.
|
|
151
|
+
* Cloudly derives clusterId and sourceServiceId from the authoritative backup,
|
|
152
|
+
* atomically acquires its retention hold, and owns every generated target id.
|
|
153
|
+
*/
|
|
154
|
+
export interface IReq_Any_Cloudly_CreateIsolatedRestore extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_CreateIsolatedRestore> {
|
|
155
|
+
method: 'createIsolatedRestore';
|
|
156
|
+
request: {
|
|
157
|
+
identity: IIdentity;
|
|
158
|
+
sourceBackupId: string;
|
|
159
|
+
targetNodeName: string;
|
|
160
|
+
purpose: string;
|
|
161
|
+
/** Caller-generated key; retries return the original restore. */
|
|
162
|
+
idempotencyKey: string;
|
|
163
|
+
/** Requested lifetime; Cloudly applies its configured maximum. */
|
|
164
|
+
ttlMs?: number;
|
|
165
|
+
/** Derived from the authoritative backup. */
|
|
166
|
+
clusterId?: never;
|
|
167
|
+
/** Derived from the authoritative backup. */
|
|
168
|
+
sourceServiceId?: never;
|
|
169
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
170
|
+
id?: never;
|
|
171
|
+
/** Alias-like caller input is forbidden as well. */
|
|
172
|
+
restoreId?: never;
|
|
173
|
+
/** Legacy runnable-service targeting is forbidden. */
|
|
174
|
+
targetServiceId?: never;
|
|
175
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
176
|
+
scratchNamespaceId?: never;
|
|
177
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
178
|
+
stagingArchiveId?: never;
|
|
179
|
+
/** Generated after validating every snapshot in the source backup. */
|
|
180
|
+
resourceMappings?: never;
|
|
181
|
+
};
|
|
182
|
+
response: {
|
|
183
|
+
restore: IIsolatedRestoreRecord;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export interface IReq_Any_Cloudly_GetIsolatedRestores extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetIsolatedRestores> {
|
|
187
|
+
method: 'getIsolatedRestores';
|
|
188
|
+
request: {
|
|
189
|
+
identity: IIdentity;
|
|
190
|
+
sourceBackupId?: string;
|
|
191
|
+
sourceServiceId?: string;
|
|
192
|
+
clusterId?: string;
|
|
193
|
+
targetNodeName?: string;
|
|
194
|
+
scratchNamespaceId?: string;
|
|
195
|
+
status?: IIsolatedRestoreRecord['status'];
|
|
196
|
+
includeCleaned?: boolean;
|
|
197
|
+
/** Opaque cursor returned by the previous page. */
|
|
198
|
+
cursor?: string;
|
|
199
|
+
/** Server-capped page size. */
|
|
200
|
+
limit?: number;
|
|
201
|
+
};
|
|
202
|
+
response: {
|
|
203
|
+
restores: IIsolatedRestoreRecord[];
|
|
204
|
+
/** Omitted when this is the final page. */
|
|
205
|
+
nextCursor?: string;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
export interface IReq_Any_Cloudly_GetIsolatedRestoreById extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetIsolatedRestoreById> {
|
|
209
|
+
method: 'getIsolatedRestoreById';
|
|
210
|
+
request: {
|
|
211
|
+
identity: IIdentity;
|
|
212
|
+
restoreId: string;
|
|
213
|
+
};
|
|
214
|
+
response: {
|
|
215
|
+
restore: IIsolatedRestoreRecord;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Prepares and stages one isolated restore on an exact cluster node. A missing
|
|
220
|
+
* node state has revision 0; successful mutation returns the next revision.
|
|
221
|
+
*/
|
|
222
|
+
export interface IReq_Cloudly_Coreflow_PrepareIsolatedRestore extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Coreflow_PrepareIsolatedRestore> {
|
|
223
|
+
method: 'coreflowPrepareIsolatedRestore';
|
|
224
|
+
request: {
|
|
225
|
+
clusterId: string;
|
|
226
|
+
targetNodeName: string;
|
|
227
|
+
restoreId: string;
|
|
228
|
+
expectedRevision: number;
|
|
229
|
+
sourceBackupId: string;
|
|
230
|
+
sourceServiceId: string;
|
|
231
|
+
scratchNamespaceId: string;
|
|
232
|
+
stagingArchiveId: string;
|
|
233
|
+
resourceMappings: TIsolatedRestoreResourceMapping[];
|
|
234
|
+
archiveManifest: IBackupArchiveManifest;
|
|
235
|
+
};
|
|
236
|
+
response: {
|
|
237
|
+
state: IIsolatedRestoreNodeState;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** Read-only status for one exact cluster/node/restore routing key. */
|
|
241
|
+
export interface IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus> {
|
|
242
|
+
method: 'coreflowGetIsolatedRestoreStatus';
|
|
243
|
+
request: {
|
|
244
|
+
clusterId: string;
|
|
245
|
+
targetNodeName: string;
|
|
246
|
+
restoreId: string;
|
|
247
|
+
};
|
|
248
|
+
response: {
|
|
249
|
+
found: boolean;
|
|
250
|
+
state?: IIsolatedRestoreNodeState;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
export interface IReq_Cloudly_Coreflow_ExecuteIsolatedRestore extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Coreflow_ExecuteIsolatedRestore> {
|
|
254
|
+
method: 'coreflowExecuteIsolatedRestore';
|
|
255
|
+
request: {
|
|
256
|
+
clusterId: string;
|
|
257
|
+
targetNodeName: string;
|
|
258
|
+
restoreId: string;
|
|
259
|
+
expectedRevision: number;
|
|
260
|
+
};
|
|
261
|
+
response: {
|
|
262
|
+
state: IIsolatedRestoreNodeState;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Idempotent cleanup mutation. A cleanup-failed state remains retryable and
|
|
267
|
+
* does not authorize release of the source-backup hold.
|
|
268
|
+
*/
|
|
269
|
+
export interface IReq_Cloudly_Coreflow_CleanupIsolatedRestore extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Coreflow_CleanupIsolatedRestore> {
|
|
270
|
+
method: 'coreflowCleanupIsolatedRestore';
|
|
271
|
+
request: {
|
|
272
|
+
clusterId: string;
|
|
273
|
+
targetNodeName: string;
|
|
274
|
+
restoreId: string;
|
|
275
|
+
expectedRevision: number;
|
|
276
|
+
};
|
|
277
|
+
response: {
|
|
278
|
+
state: IIsolatedRestoreNodeState;
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Requests idempotent asynchronous cleanup. The restore record is retained as
|
|
283
|
+
* audit evidence and transitions through the canonical cleanup statuses.
|
|
284
|
+
*/
|
|
285
|
+
export interface IReq_Any_Cloudly_CleanupIsolatedRestore extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_CleanupIsolatedRestore> {
|
|
286
|
+
method: 'cleanupIsolatedRestore';
|
|
287
|
+
request: {
|
|
288
|
+
identity: IIdentity;
|
|
289
|
+
restoreId: string;
|
|
290
|
+
reason: string;
|
|
291
|
+
};
|
|
292
|
+
response: {
|
|
293
|
+
restore: IIsolatedRestoreRecord;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
@@ -17,7 +17,7 @@ export interface IRequest_Any_Cloudly_GetServerConfig extends plugins.typedreque
|
|
|
17
17
|
export interface IRequest_Any_Cloudly_GetClusterConfig extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetClusterConfig> {
|
|
18
18
|
method: 'getClusterConfig';
|
|
19
19
|
request: {
|
|
20
|
-
identity: userInterfaces.
|
|
20
|
+
identity: userInterfaces.IIdentityCredential;
|
|
21
21
|
};
|
|
22
22
|
response: {
|
|
23
23
|
configData: clusterInterfaces.ICluster;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/interfaces",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@git.zone/tsdoc": "^2.1.1",
|
|
20
20
|
"@git.zone/tsrun": "^2.0.5",
|
|
21
21
|
"@git.zone/tstest": "^3.6.6",
|
|
22
|
-
"@types/node": "^26.0
|
|
22
|
+
"@types/node": "^26.1.0"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"ts/**/*",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
],
|
|
53
53
|
"scripts": {
|
|
54
54
|
"test": "tstest test/ --verbose --logfile --timeout 60",
|
|
55
|
-
"build": "tsbuild tsfolders
|
|
55
|
+
"build": "tsbuild tsfolders",
|
|
56
56
|
"buildDocs": "tsdoc"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/readme.md
CHANGED
|
@@ -99,7 +99,7 @@ Common data contracts include:
|
|
|
99
99
|
- `ISecretBundle` and `ISecretGroup` for secret ownership and shared secret groups.
|
|
100
100
|
- Mail gateway contracts for domain authorities, address bindings, WorkApp bindings, managed SMTP/API credentials, spool items, delivery journals, and inbound/outbound message payloads.
|
|
101
101
|
- Service-level mail configuration through `IService.data.mail`, including per-address inbound `smtpForward` settings and outbound credential metadata. Cloudly settings include dcrouter gateway, SMTP submission, and inbound forward-target keys for reconciling those bindings.
|
|
102
|
-
- `IUser`, `IIdentity`, and token-related contracts for authentication context.
|
|
102
|
+
- `IUser`, JWT-only `IIdentityCredential`, full `IIdentity`, and token-related contracts for authentication context. `IIdentity` extends `IIdentityCredential` with server-issued user metadata.
|
|
103
103
|
- `ICloudlyConfig`, `ICloudlySettings`, status, server, bare-metal, BaseOS, backup, and task execution interfaces for control-plane state.
|
|
104
104
|
|
|
105
105
|
Shared service port helpers are exported from `data` so Cloudly, App Store resolution, Coreflow, Coretraffic, Onebox, and dcrouter agree on the same normalization rules:
|
|
@@ -138,6 +138,8 @@ interface IExampleRequest {
|
|
|
138
138
|
}
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
`requests.config.IRequest_Any_Cloudly_GetClusterConfig` accepts `data.IIdentityCredential`, which contains only the JWT needed for server-side identity resolution. Username/password login and machine-token exchange responses continue to return the full `data.IIdentity`.
|
|
142
|
+
|
|
141
143
|
Gateway request contracts include `getGatewayClientRoutes` (`requests.gateway.IReq_GetGatewayClientRoutes`) for listing owned `IGatewayClientRoute[]` route views, and `syncGatewayClientRoute` for idempotently syncing or deleting hostname-owned and routeRef-owned routes. Mail request contracts include `syncMailAddressBinding`, `deleteMailAddressBinding`, `rotateMailCredential`, and `getMailDeliveryStatus`. `IReq_GetMailDeliveryStatus` looks up a delivery spool item by `spoolItemId`, returns `data.IMailDeliveryStatus`, and accepts `IMailSubmissionRequestAuth` so service-mail credentials can query their own accepted, queued, deferred, delivered, or failed status. `TMailAddressBindingSync.outboundEnabled` explicitly controls whether a gateway should maintain a managed outbound SMTP credential for an address binding. Binding credential metadata is public; `rotateMailCredential` returns the new secret only in its one-time `IMailCredentialOneTimeSecret` response.
|
|
142
144
|
|
|
143
145
|
Request groups are exported by product area:
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/data/backup.ts
CHANGED
|
@@ -77,6 +77,20 @@ export interface IBackupRestoreEvent {
|
|
|
77
77
|
errorText?: string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Durable retention hold owned by one isolated restore. An entry is active
|
|
82
|
+
* while `releasedAt` is absent. Active holds never expire implicitly:
|
|
83
|
+
* retention may neither claim nor delete the backup until every active hold
|
|
84
|
+
* has been released after successful scratch-resource and staging cleanup.
|
|
85
|
+
*/
|
|
86
|
+
export interface IBackupRestoreHold {
|
|
87
|
+
restoreId: string;
|
|
88
|
+
idempotencyKey: string;
|
|
89
|
+
acquiredAt: number;
|
|
90
|
+
releasedAt?: number;
|
|
91
|
+
releaseReason?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
export interface IBackupRecord {
|
|
81
95
|
id: string;
|
|
82
96
|
serviceId: string;
|
|
@@ -100,6 +114,12 @@ export interface IBackupRecord {
|
|
|
100
114
|
requestedBy?: string;
|
|
101
115
|
errorText?: string;
|
|
102
116
|
restoreHistory?: IBackupRestoreEvent[];
|
|
117
|
+
/**
|
|
118
|
+
* Authoritative isolated-restore holds. Cloudly atomically appends the
|
|
119
|
+
* active entry with restore creation. Failed archive cleanup must retain
|
|
120
|
+
* this backup record so hold recovery remains possible.
|
|
121
|
+
*/
|
|
122
|
+
restoreHolds?: IBackupRestoreHold[];
|
|
103
123
|
tags?: Record<string, string>;
|
|
104
124
|
}
|
|
105
125
|
|
|
@@ -118,3 +138,291 @@ export interface IArchivePruneResult {
|
|
|
118
138
|
freedBytes: number;
|
|
119
139
|
dryRun: boolean;
|
|
120
140
|
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* JWT-free actor projection persisted with isolated-restore audit data.
|
|
144
|
+
* Authentication credentials must never be copied into restore records.
|
|
145
|
+
*/
|
|
146
|
+
export interface IIsolatedRestoreActor {
|
|
147
|
+
userId: string;
|
|
148
|
+
type: 'human' | 'machine' | 'system';
|
|
149
|
+
role?: 'admin' | 'user' | 'api' | 'cluster';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type TIsolatedRestoreStatus =
|
|
153
|
+
| 'pending'
|
|
154
|
+
| 'preparing'
|
|
155
|
+
| 'staging'
|
|
156
|
+
| 'restoring'
|
|
157
|
+
| 'verifying'
|
|
158
|
+
| 'ready'
|
|
159
|
+
| 'failed'
|
|
160
|
+
| 'cleanup-pending'
|
|
161
|
+
| 'cleaning'
|
|
162
|
+
| 'cleaned'
|
|
163
|
+
| 'cleanup-failed';
|
|
164
|
+
|
|
165
|
+
export type TIsolatedRestoreErrorPhase =
|
|
166
|
+
| 'validation'
|
|
167
|
+
| 'source-backup-hold'
|
|
168
|
+
| 'preparation'
|
|
169
|
+
| 'staging'
|
|
170
|
+
| 'restoring'
|
|
171
|
+
| 'verifying'
|
|
172
|
+
| 'cleanup';
|
|
173
|
+
|
|
174
|
+
/** Append-only structured failure entry for one isolated restore. */
|
|
175
|
+
export interface IIsolatedRestoreError {
|
|
176
|
+
sequence: number;
|
|
177
|
+
phase: TIsolatedRestoreErrorPhase;
|
|
178
|
+
code: string;
|
|
179
|
+
message: string;
|
|
180
|
+
retryable: boolean;
|
|
181
|
+
occurredAt: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type TIsolatedRestoreAuditAction =
|
|
185
|
+
| 'created'
|
|
186
|
+
| 'status-transitioned'
|
|
187
|
+
| 'source-backup-hold-acquired'
|
|
188
|
+
| 'cleanup-requested'
|
|
189
|
+
| 'expired'
|
|
190
|
+
| 'source-backup-hold-release-requested'
|
|
191
|
+
| 'source-backup-hold-released';
|
|
192
|
+
|
|
193
|
+
/** Append-only lifecycle event. `sequence` is strictly increasing per restore. */
|
|
194
|
+
export interface IIsolatedRestoreAuditEvent {
|
|
195
|
+
sequence: number;
|
|
196
|
+
action: TIsolatedRestoreAuditAction;
|
|
197
|
+
actor: IIsolatedRestoreActor;
|
|
198
|
+
occurredAt: number;
|
|
199
|
+
fromStatus?: TIsolatedRestoreStatus;
|
|
200
|
+
toStatus?: TIsolatedRestoreStatus;
|
|
201
|
+
reason?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface IIsolatedRestoreVolumeMapping {
|
|
205
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
206
|
+
id: string;
|
|
207
|
+
type: 'volume';
|
|
208
|
+
source: {
|
|
209
|
+
snapshotId: string;
|
|
210
|
+
volumeName: string;
|
|
211
|
+
mountPath: string;
|
|
212
|
+
};
|
|
213
|
+
target: {
|
|
214
|
+
volumeName: string;
|
|
215
|
+
mountPath: string;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface IIsolatedRestoreDatabaseMapping {
|
|
220
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
221
|
+
id: string;
|
|
222
|
+
type: 'database';
|
|
223
|
+
source: {
|
|
224
|
+
snapshotId: string;
|
|
225
|
+
resourceName: string;
|
|
226
|
+
databaseName: string;
|
|
227
|
+
};
|
|
228
|
+
target: {
|
|
229
|
+
resourceName: string;
|
|
230
|
+
databaseName: string;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface IIsolatedRestoreObjectStorageMapping {
|
|
235
|
+
/** Stable server-generated id used by verification and cleanup progress. */
|
|
236
|
+
id: string;
|
|
237
|
+
type: 'objectstorage';
|
|
238
|
+
source: {
|
|
239
|
+
snapshotId: string;
|
|
240
|
+
resourceName: string;
|
|
241
|
+
bucketName: string;
|
|
242
|
+
};
|
|
243
|
+
target: {
|
|
244
|
+
resourceName: string;
|
|
245
|
+
bucketName: string;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export type TIsolatedRestoreResourceMapping =
|
|
250
|
+
| IIsolatedRestoreVolumeMapping
|
|
251
|
+
| IIsolatedRestoreDatabaseMapping
|
|
252
|
+
| IIsolatedRestoreObjectStorageMapping;
|
|
253
|
+
|
|
254
|
+
export type TIsolatedRestoreVerificationStatus =
|
|
255
|
+
| 'pending'
|
|
256
|
+
| 'running'
|
|
257
|
+
| 'passed'
|
|
258
|
+
| 'failed';
|
|
259
|
+
|
|
260
|
+
export interface IIsolatedRestoreVerificationCheck {
|
|
261
|
+
/** Stable machine-readable check code. */
|
|
262
|
+
code: string;
|
|
263
|
+
status: 'passed' | 'failed';
|
|
264
|
+
message?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface IIsolatedRestoreResourceVerification {
|
|
268
|
+
resourceMappingId: string;
|
|
269
|
+
resourceType: TBackupResourceType;
|
|
270
|
+
status: TIsolatedRestoreVerificationStatus;
|
|
271
|
+
checks: IIsolatedRestoreVerificationCheck[];
|
|
272
|
+
checkedAt?: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Complete verification state returned by the target data plane. */
|
|
276
|
+
export interface IIsolatedRestoreVerification {
|
|
277
|
+
status: TIsolatedRestoreVerificationStatus;
|
|
278
|
+
results: IIsolatedRestoreResourceVerification[];
|
|
279
|
+
startedAt?: number;
|
|
280
|
+
completedAt?: number;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export type TIsolatedRestoreCleanupTrigger =
|
|
284
|
+
| 'manual'
|
|
285
|
+
| 'expired'
|
|
286
|
+
| 'restore-failed'
|
|
287
|
+
| 'policy';
|
|
288
|
+
|
|
289
|
+
export type TIsolatedRestoreCleanupProgressStatus =
|
|
290
|
+
| 'pending'
|
|
291
|
+
| 'running'
|
|
292
|
+
| 'succeeded'
|
|
293
|
+
| 'failed';
|
|
294
|
+
|
|
295
|
+
export interface IIsolatedRestoreResourceCleanupProgress {
|
|
296
|
+
resourceMappingId: string;
|
|
297
|
+
resourceType: TBackupResourceType;
|
|
298
|
+
status: TIsolatedRestoreCleanupProgressStatus;
|
|
299
|
+
attempts: number;
|
|
300
|
+
updatedAt?: number;
|
|
301
|
+
errorCode?: string;
|
|
302
|
+
errorMessage?: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface IIsolatedRestoreStagingCleanupProgress {
|
|
306
|
+
stagingArchiveId: string;
|
|
307
|
+
status: TIsolatedRestoreCleanupProgressStatus;
|
|
308
|
+
attempts: number;
|
|
309
|
+
updatedAt?: number;
|
|
310
|
+
errorCode?: string;
|
|
311
|
+
errorMessage?: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Data-plane cleanup progress shared by public and internal status views. */
|
|
315
|
+
export interface IIsolatedRestoreCleanupProgress {
|
|
316
|
+
resources: IIsolatedRestoreResourceCleanupProgress[];
|
|
317
|
+
stagingArchive: IIsolatedRestoreStagingCleanupProgress;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Cleanup metadata. The source-backup hold remains active until every
|
|
322
|
+
* resource entry and the staging entry report `succeeded`. `cleanup-failed`
|
|
323
|
+
* is retryable and must not release the hold.
|
|
324
|
+
*/
|
|
325
|
+
export interface IIsolatedRestoreCleanup extends IIsolatedRestoreCleanupProgress {
|
|
326
|
+
trigger: TIsolatedRestoreCleanupTrigger;
|
|
327
|
+
reason: string;
|
|
328
|
+
requestedBy: IIsolatedRestoreActor;
|
|
329
|
+
requestedAt: number;
|
|
330
|
+
startedAt?: number;
|
|
331
|
+
completedAt?: number;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export type TIsolatedRestoreSourceBackupHoldStatus =
|
|
335
|
+
| 'acquired'
|
|
336
|
+
| 'release-pending'
|
|
337
|
+
| 'released';
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Restore-side projection of the authoritative IBackupRecord hold. The hold
|
|
341
|
+
* is non-expiring and may enter `released` only after exact cleanup success.
|
|
342
|
+
*/
|
|
343
|
+
export interface IIsolatedRestoreSourceBackupHold {
|
|
344
|
+
status: TIsolatedRestoreSourceBackupHoldStatus;
|
|
345
|
+
acquiredAt: number;
|
|
346
|
+
releaseRequestedAt?: number;
|
|
347
|
+
releasedAt?: number;
|
|
348
|
+
releaseReason?: string;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export type TIsolatedRestoreNodeStatus =
|
|
352
|
+
| 'preparing'
|
|
353
|
+
| 'prepared'
|
|
354
|
+
| 'staging'
|
|
355
|
+
| 'restoring'
|
|
356
|
+
| 'verifying'
|
|
357
|
+
| 'ready'
|
|
358
|
+
| 'failed'
|
|
359
|
+
| 'cleaning'
|
|
360
|
+
| 'cleaned'
|
|
361
|
+
| 'cleanup-failed';
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Data-plane state for one exact cluster/node/restore routing key. `revision`
|
|
365
|
+
* advances after every mutation and is the compare-and-swap value used by
|
|
366
|
+
* prepare, execute, and cleanup requests.
|
|
367
|
+
*/
|
|
368
|
+
export interface IIsolatedRestoreNodeState {
|
|
369
|
+
restoreId: string;
|
|
370
|
+
clusterId: string;
|
|
371
|
+
targetNodeName: string;
|
|
372
|
+
sourceBackupId: string;
|
|
373
|
+
sourceServiceId: string;
|
|
374
|
+
scratchNamespaceId: string;
|
|
375
|
+
stagingArchiveId: string;
|
|
376
|
+
revision: number;
|
|
377
|
+
status: TIsolatedRestoreNodeStatus;
|
|
378
|
+
completedResourceMappingIds: string[];
|
|
379
|
+
verification: IIsolatedRestoreVerification;
|
|
380
|
+
cleanup?: IIsolatedRestoreCleanupProgress;
|
|
381
|
+
preparedAt?: number;
|
|
382
|
+
readyAt?: number;
|
|
383
|
+
updatedAt: number;
|
|
384
|
+
lastErrorCode?: string;
|
|
385
|
+
lastErrorMessage?: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Durable control-plane record for a restore into a generated data-plane
|
|
390
|
+
* scratch namespace. The namespace is not an IService and carries no image,
|
|
391
|
+
* runtime, route, port, environment, or secret authority.
|
|
392
|
+
*
|
|
393
|
+
* `clusterId` and `sourceServiceId` are copied from the authoritative source
|
|
394
|
+
* backup, never accepted from the caller. `revision` advances monotonically
|
|
395
|
+
* for optimistic lifecycle updates. A restore execution failure (`failed`)
|
|
396
|
+
* must proceed through cleanup before its source-backup hold can be released.
|
|
397
|
+
*/
|
|
398
|
+
export interface IIsolatedRestoreRecord {
|
|
399
|
+
/** Server-generated restore id. */
|
|
400
|
+
id: string;
|
|
401
|
+
/** Caller key used to return the same restore after retried creation. */
|
|
402
|
+
idempotencyKey: string;
|
|
403
|
+
revision: number;
|
|
404
|
+
sourceBackupId: string;
|
|
405
|
+
sourceServiceId: string;
|
|
406
|
+
sourceServiceName?: string;
|
|
407
|
+
clusterId: string;
|
|
408
|
+
/** Server-generated, non-runnable data-plane namespace. */
|
|
409
|
+
scratchNamespaceId: string;
|
|
410
|
+
/** Server-generated archive namespace, distinct from sourceBackupId. */
|
|
411
|
+
stagingArchiveId: string;
|
|
412
|
+
targetNodeName: string;
|
|
413
|
+
purpose: string;
|
|
414
|
+
requestedBy: IIsolatedRestoreActor;
|
|
415
|
+
status: TIsolatedRestoreStatus;
|
|
416
|
+
resourceMappings: TIsolatedRestoreResourceMapping[];
|
|
417
|
+
verification: IIsolatedRestoreVerification;
|
|
418
|
+
sourceBackupHold: IIsolatedRestoreSourceBackupHold;
|
|
419
|
+
createdAt: number;
|
|
420
|
+
updatedAt: number;
|
|
421
|
+
expiresAt: number;
|
|
422
|
+
readyAt?: number;
|
|
423
|
+
cleanup?: IIsolatedRestoreCleanup;
|
|
424
|
+
/** Append-only; entries are never replaced when a later phase fails. */
|
|
425
|
+
errors: IIsolatedRestoreError[];
|
|
426
|
+
/** Append-only lifecycle audit trail. */
|
|
427
|
+
auditEvents: IIsolatedRestoreAuditEvent[];
|
|
428
|
+
}
|
package/ts/data/user.ts
CHANGED
|
@@ -4,18 +4,20 @@ export interface IToken {
|
|
|
4
4
|
assignedRoles: string[];
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export interface IIdentityCredential {
|
|
8
|
+
jwt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* an identity is assumed by authentication as a user
|
|
9
13
|
* an identity is ephemeral and has to be renewed regularly
|
|
10
14
|
*/
|
|
11
|
-
export interface IIdentity {
|
|
15
|
+
export interface IIdentity extends IIdentityCredential {
|
|
12
16
|
name: string;
|
|
13
17
|
userId: string;
|
|
14
18
|
type: 'machine' | 'human';
|
|
15
19
|
role: 'admin' | 'user' | 'api' | 'cluster';
|
|
16
20
|
expiresAt: number;
|
|
17
|
-
/** the jwt token should contain above data for verification */
|
|
18
|
-
jwt: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface IUser {
|
|
@@ -27,4 +29,4 @@ export interface IUser {
|
|
|
27
29
|
password?: string;
|
|
28
30
|
tokens?: IToken[];
|
|
29
31
|
}
|
|
30
|
-
}
|
|
32
|
+
}
|
package/ts/requests/backup.ts
CHANGED
|
@@ -6,7 +6,10 @@ import type {
|
|
|
6
6
|
IBackupArchiveObject,
|
|
7
7
|
IBackupRecord,
|
|
8
8
|
IBackupReplicationResult,
|
|
9
|
+
IIsolatedRestoreNodeState,
|
|
10
|
+
IIsolatedRestoreRecord,
|
|
9
11
|
TBackupSnapshot,
|
|
12
|
+
TIsolatedRestoreResourceMapping,
|
|
10
13
|
} from '../data/backup.js';
|
|
11
14
|
import type { IService } from '../data/service.js';
|
|
12
15
|
import type { IIdentity } from '../data/user.js';
|
|
@@ -214,3 +217,190 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
214
217
|
result?: IArchivePruneResult;
|
|
215
218
|
};
|
|
216
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Creates a restore in a new non-runnable data-plane scratch namespace.
|
|
223
|
+
* Cloudly derives clusterId and sourceServiceId from the authoritative backup,
|
|
224
|
+
* atomically acquires its retention hold, and owns every generated target id.
|
|
225
|
+
*/
|
|
226
|
+
export interface IReq_Any_Cloudly_CreateIsolatedRestore
|
|
227
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
228
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
229
|
+
IReq_Any_Cloudly_CreateIsolatedRestore
|
|
230
|
+
> {
|
|
231
|
+
method: 'createIsolatedRestore';
|
|
232
|
+
request: {
|
|
233
|
+
identity: IIdentity;
|
|
234
|
+
sourceBackupId: string;
|
|
235
|
+
targetNodeName: string;
|
|
236
|
+
purpose: string;
|
|
237
|
+
/** Caller-generated key; retries return the original restore. */
|
|
238
|
+
idempotencyKey: string;
|
|
239
|
+
/** Requested lifetime; Cloudly applies its configured maximum. */
|
|
240
|
+
ttlMs?: number;
|
|
241
|
+
/** Derived from the authoritative backup. */
|
|
242
|
+
clusterId?: never;
|
|
243
|
+
/** Derived from the authoritative backup. */
|
|
244
|
+
sourceServiceId?: never;
|
|
245
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
246
|
+
id?: never;
|
|
247
|
+
/** Alias-like caller input is forbidden as well. */
|
|
248
|
+
restoreId?: never;
|
|
249
|
+
/** Legacy runnable-service targeting is forbidden. */
|
|
250
|
+
targetServiceId?: never;
|
|
251
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
252
|
+
scratchNamespaceId?: never;
|
|
253
|
+
/** Generated by Cloudly. Explicitly forbidden as caller input. */
|
|
254
|
+
stagingArchiveId?: never;
|
|
255
|
+
/** Generated after validating every snapshot in the source backup. */
|
|
256
|
+
resourceMappings?: never;
|
|
257
|
+
};
|
|
258
|
+
response: {
|
|
259
|
+
restore: IIsolatedRestoreRecord;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface IReq_Any_Cloudly_GetIsolatedRestores
|
|
264
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
265
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
266
|
+
IReq_Any_Cloudly_GetIsolatedRestores
|
|
267
|
+
> {
|
|
268
|
+
method: 'getIsolatedRestores';
|
|
269
|
+
request: {
|
|
270
|
+
identity: IIdentity;
|
|
271
|
+
sourceBackupId?: string;
|
|
272
|
+
sourceServiceId?: string;
|
|
273
|
+
clusterId?: string;
|
|
274
|
+
targetNodeName?: string;
|
|
275
|
+
scratchNamespaceId?: string;
|
|
276
|
+
status?: IIsolatedRestoreRecord['status'];
|
|
277
|
+
includeCleaned?: boolean;
|
|
278
|
+
/** Opaque cursor returned by the previous page. */
|
|
279
|
+
cursor?: string;
|
|
280
|
+
/** Server-capped page size. */
|
|
281
|
+
limit?: number;
|
|
282
|
+
};
|
|
283
|
+
response: {
|
|
284
|
+
restores: IIsolatedRestoreRecord[];
|
|
285
|
+
/** Omitted when this is the final page. */
|
|
286
|
+
nextCursor?: string;
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface IReq_Any_Cloudly_GetIsolatedRestoreById
|
|
291
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
292
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
293
|
+
IReq_Any_Cloudly_GetIsolatedRestoreById
|
|
294
|
+
> {
|
|
295
|
+
method: 'getIsolatedRestoreById';
|
|
296
|
+
request: {
|
|
297
|
+
identity: IIdentity;
|
|
298
|
+
restoreId: string;
|
|
299
|
+
};
|
|
300
|
+
response: {
|
|
301
|
+
restore: IIsolatedRestoreRecord;
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Prepares and stages one isolated restore on an exact cluster node. A missing
|
|
307
|
+
* node state has revision 0; successful mutation returns the next revision.
|
|
308
|
+
*/
|
|
309
|
+
export interface IReq_Cloudly_Coreflow_PrepareIsolatedRestore
|
|
310
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
311
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
312
|
+
IReq_Cloudly_Coreflow_PrepareIsolatedRestore
|
|
313
|
+
> {
|
|
314
|
+
method: 'coreflowPrepareIsolatedRestore';
|
|
315
|
+
request: {
|
|
316
|
+
clusterId: string;
|
|
317
|
+
targetNodeName: string;
|
|
318
|
+
restoreId: string;
|
|
319
|
+
expectedRevision: number;
|
|
320
|
+
sourceBackupId: string;
|
|
321
|
+
sourceServiceId: string;
|
|
322
|
+
scratchNamespaceId: string;
|
|
323
|
+
stagingArchiveId: string;
|
|
324
|
+
resourceMappings: TIsolatedRestoreResourceMapping[];
|
|
325
|
+
archiveManifest: IBackupArchiveManifest;
|
|
326
|
+
};
|
|
327
|
+
response: {
|
|
328
|
+
state: IIsolatedRestoreNodeState;
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Read-only status for one exact cluster/node/restore routing key. */
|
|
333
|
+
export interface IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus
|
|
334
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
335
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
336
|
+
IReq_Cloudly_Coreflow_GetIsolatedRestoreStatus
|
|
337
|
+
> {
|
|
338
|
+
method: 'coreflowGetIsolatedRestoreStatus';
|
|
339
|
+
request: {
|
|
340
|
+
clusterId: string;
|
|
341
|
+
targetNodeName: string;
|
|
342
|
+
restoreId: string;
|
|
343
|
+
};
|
|
344
|
+
response: {
|
|
345
|
+
found: boolean;
|
|
346
|
+
state?: IIsolatedRestoreNodeState;
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface IReq_Cloudly_Coreflow_ExecuteIsolatedRestore
|
|
351
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
352
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
353
|
+
IReq_Cloudly_Coreflow_ExecuteIsolatedRestore
|
|
354
|
+
> {
|
|
355
|
+
method: 'coreflowExecuteIsolatedRestore';
|
|
356
|
+
request: {
|
|
357
|
+
clusterId: string;
|
|
358
|
+
targetNodeName: string;
|
|
359
|
+
restoreId: string;
|
|
360
|
+
expectedRevision: number;
|
|
361
|
+
};
|
|
362
|
+
response: {
|
|
363
|
+
state: IIsolatedRestoreNodeState;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Idempotent cleanup mutation. A cleanup-failed state remains retryable and
|
|
369
|
+
* does not authorize release of the source-backup hold.
|
|
370
|
+
*/
|
|
371
|
+
export interface IReq_Cloudly_Coreflow_CleanupIsolatedRestore
|
|
372
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
373
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
374
|
+
IReq_Cloudly_Coreflow_CleanupIsolatedRestore
|
|
375
|
+
> {
|
|
376
|
+
method: 'coreflowCleanupIsolatedRestore';
|
|
377
|
+
request: {
|
|
378
|
+
clusterId: string;
|
|
379
|
+
targetNodeName: string;
|
|
380
|
+
restoreId: string;
|
|
381
|
+
expectedRevision: number;
|
|
382
|
+
};
|
|
383
|
+
response: {
|
|
384
|
+
state: IIsolatedRestoreNodeState;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Requests idempotent asynchronous cleanup. The restore record is retained as
|
|
390
|
+
* audit evidence and transitions through the canonical cleanup statuses.
|
|
391
|
+
*/
|
|
392
|
+
export interface IReq_Any_Cloudly_CleanupIsolatedRestore
|
|
393
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
394
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
395
|
+
IReq_Any_Cloudly_CleanupIsolatedRestore
|
|
396
|
+
> {
|
|
397
|
+
method: 'cleanupIsolatedRestore';
|
|
398
|
+
request: {
|
|
399
|
+
identity: IIdentity;
|
|
400
|
+
restoreId: string;
|
|
401
|
+
reason: string;
|
|
402
|
+
};
|
|
403
|
+
response: {
|
|
404
|
+
restore: IIsolatedRestoreRecord;
|
|
405
|
+
};
|
|
406
|
+
}
|
package/ts/requests/config.ts
CHANGED