@claudian-collab/protocol 3.1.0 → 3.2.1
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/README.md +7 -5
- package/dist/CollabProjectBackupCheckpoint.d.ts +164 -0
- package/dist/CollabProjectBackupCheckpoint.js +1483 -0
- package/dist/esm/CollabAuthorityTransfer.mjs +884 -0
- package/dist/esm/CollabCloudBinding.mjs +552 -0
- package/dist/esm/CollabCloudProjectEvent.mjs +150 -0
- package/dist/esm/CollabCloudProjectSnapshot.mjs +277 -0
- package/dist/esm/CollabConstants.mjs +31 -0
- package/dist/esm/CollabControlOperationCodecs.mjs +104 -0
- package/dist/esm/CollabError.mjs +175 -0
- package/dist/esm/CollabMarkdownProse.mjs +54 -0
- package/dist/esm/CollabMemberMentionParser.mjs +48 -0
- package/dist/esm/CollabProjectBackupCheckpoint.mjs +1473 -0
- package/dist/esm/CollabProjectCheckpoint.mjs +1326 -0
- package/dist/esm/CollabProjectRetirement.mjs +119 -0
- package/dist/esm/CollabProtocol.mjs +64 -0
- package/dist/esm/CollabRequestTicketRequestCodecs.mjs +263 -0
- package/dist/esm/CollabRequestTicketResponseCodecs.mjs +353 -0
- package/dist/esm/CollabTicketReferenceParser.mjs +52 -0
- package/dist/esm/CollabValidation.mjs +19 -0
- package/dist/esm/DevelopmentBootstrap.mjs +537 -0
- package/dist/esm/index.mjs +16 -0
- package/dist/esm/types.mjs +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/package.json +5 -2
|
@@ -0,0 +1,1483 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLAB_PROJECT_BACKUP_RECORD_KINDS = exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION = void 0;
|
|
4
|
+
exports.collabProjectBackupIdempotencyRecordId = collabProjectBackupIdempotencyRecordId;
|
|
5
|
+
exports.decodeCollabProjectBackupCheckpointManifest = decodeCollabProjectBackupCheckpointManifest;
|
|
6
|
+
exports.encodeCollabProjectBackupCheckpointManifestCanonicalJson = encodeCollabProjectBackupCheckpointManifestCanonicalJson;
|
|
7
|
+
exports.encodeCollabProjectBackupCheckpointManifestDigestInput = encodeCollabProjectBackupCheckpointManifestDigestInput;
|
|
8
|
+
exports.decodeCollabProjectBackupCheckpointCoordinationNdjson = decodeCollabProjectBackupCheckpointCoordinationNdjson;
|
|
9
|
+
exports.encodeCollabProjectBackupCheckpointCoordinationNdjson = encodeCollabProjectBackupCheckpointCoordinationNdjson;
|
|
10
|
+
exports.validateCollabProjectBackupCheckpointConsistency = validateCollabProjectBackupCheckpointConsistency;
|
|
11
|
+
const CollabAuthorityTransfer_1 = require("./CollabAuthorityTransfer");
|
|
12
|
+
const CollabConstants_1 = require("./CollabConstants");
|
|
13
|
+
const CollabControlOperationCodecs_1 = require("./CollabControlOperationCodecs");
|
|
14
|
+
const CollabError_1 = require("./CollabError");
|
|
15
|
+
const CollabProjectCheckpoint_1 = require("./CollabProjectCheckpoint");
|
|
16
|
+
const CollabValidation_1 = require("./CollabValidation");
|
|
17
|
+
exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION = 2;
|
|
18
|
+
const BACKUP_CONTINUITY_RECORD_KINDS = Object.freeze([
|
|
19
|
+
'lifecycle-journal',
|
|
20
|
+
'authority-transfer-recovery',
|
|
21
|
+
'transferred-membership-claim',
|
|
22
|
+
'transfer-receipt-key',
|
|
23
|
+
'transfer-claim-batch-receipt',
|
|
24
|
+
'transfer-redemption-receipt',
|
|
25
|
+
'terminal-principal',
|
|
26
|
+
'terminal-responder-replay',
|
|
27
|
+
'leave-former-principal-replay',
|
|
28
|
+
]);
|
|
29
|
+
exports.COLLAB_PROJECT_BACKUP_RECORD_KINDS = Object.freeze([
|
|
30
|
+
...CollabProjectCheckpoint_1.COLLAB_CHECKPOINT_BACKUP_RECORD_KINDS.slice(0, 13),
|
|
31
|
+
...BACKUP_CONTINUITY_RECORD_KINDS,
|
|
32
|
+
...CollabProjectCheckpoint_1.COLLAB_CHECKPOINT_BACKUP_RECORD_KINDS.slice(14),
|
|
33
|
+
]);
|
|
34
|
+
const CONTINUITY_KIND_SET = new Set(BACKUP_CONTINUITY_RECORD_KINDS);
|
|
35
|
+
const BACKUP_KIND_SET = new Set(exports.COLLAB_PROJECT_BACKUP_RECORD_KINDS);
|
|
36
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
37
|
+
const PRINCIPAL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
38
|
+
const PHASE_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u;
|
|
39
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
40
|
+
const STORAGE_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,127}$/u;
|
|
41
|
+
const STORAGE_NODE_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
|
|
42
|
+
const CANCELLATION_PHASE_INDEX = new Map(CollabAuthorityTransfer_1.COLLAB_AUTHORITY_TRANSFER_CANCELLATION_PHASES.map((phase, index) => [phase, index]));
|
|
43
|
+
const BACKUP_EXPORT_ACTIVE_PHASE_SET = new Set([
|
|
44
|
+
'prepared',
|
|
45
|
+
'coordination-captured',
|
|
46
|
+
'repository-captured',
|
|
47
|
+
'checkpoint-verified',
|
|
48
|
+
'artifact-published',
|
|
49
|
+
'cancel-intent',
|
|
50
|
+
]);
|
|
51
|
+
const BACKUP_EXPORT_CHECKPOINT_REQUIRED_PHASE_SET = new Set([
|
|
52
|
+
'checkpoint-verified', 'artifact-published', 'completed',
|
|
53
|
+
]);
|
|
54
|
+
const DELETE_ACTIVE_PHASE_SET = new Set([
|
|
55
|
+
'traffic-denied',
|
|
56
|
+
'repository-delete-intent',
|
|
57
|
+
'repository-removed',
|
|
58
|
+
'coordination-removed',
|
|
59
|
+
'tombstoned',
|
|
60
|
+
]);
|
|
61
|
+
const PLAINTEXT_CLAIM_RESPONSE_OPERATION_SET = new Set([
|
|
62
|
+
'getTransferredMembershipClaim',
|
|
63
|
+
'rotateTransferredMembershipClaims',
|
|
64
|
+
]);
|
|
65
|
+
function invalidPayload(field) {
|
|
66
|
+
return new CollabError_1.CollabError({ code: 'protocol-payload-invalid', safeContext: { field } });
|
|
67
|
+
}
|
|
68
|
+
function record(value, field) {
|
|
69
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
70
|
+
throw invalidPayload(field);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function exactRecord(value, field, keys) {
|
|
75
|
+
const source = record(value, field);
|
|
76
|
+
const expected = new Set(keys);
|
|
77
|
+
if (!keys.every(key => Object.hasOwn(source, key))
|
|
78
|
+
|| Object.keys(source).some(key => !expected.has(key)))
|
|
79
|
+
throw invalidPayload(field);
|
|
80
|
+
return source;
|
|
81
|
+
}
|
|
82
|
+
function boundedString(source, field, maximumBytes, allowEmpty = false) {
|
|
83
|
+
const value = source[field];
|
|
84
|
+
if (typeof value !== 'string'
|
|
85
|
+
|| (!allowEmpty && value.length === 0)
|
|
86
|
+
|| !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(value, maximumBytes))
|
|
87
|
+
throw invalidPayload(field);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function token(source, field, validate = CollabValidation_1.isCollabOpaqueId) {
|
|
91
|
+
const value = source[field];
|
|
92
|
+
if (typeof value !== 'string' || !validate(value))
|
|
93
|
+
throw invalidPayload(field);
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function nullableToken(source, field, validate = CollabValidation_1.isCollabOpaqueId) {
|
|
97
|
+
return source[field] === null ? null : token(source, field, validate);
|
|
98
|
+
}
|
|
99
|
+
function positiveInteger(source, field) {
|
|
100
|
+
const value = source[field];
|
|
101
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {
|
|
102
|
+
throw invalidPayload(field);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function nullablePositiveInteger(source, field) {
|
|
107
|
+
return source[field] === null ? null : positiveInteger(source, field);
|
|
108
|
+
}
|
|
109
|
+
function timestampValue(value, field) {
|
|
110
|
+
if (typeof value !== 'string'
|
|
111
|
+
|| value.length > 64
|
|
112
|
+
|| Number.isNaN(Date.parse(value))
|
|
113
|
+
|| new Date(value).toISOString() !== value)
|
|
114
|
+
throw invalidPayload(field);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function timestamp(source, field) {
|
|
118
|
+
return timestampValue(source[field], field);
|
|
119
|
+
}
|
|
120
|
+
function nullableTimestamp(source, field) {
|
|
121
|
+
return source[field] === null ? null : timestampValue(source[field], field);
|
|
122
|
+
}
|
|
123
|
+
function sha256(source, field) {
|
|
124
|
+
const value = source[field];
|
|
125
|
+
if (typeof value !== 'string' || !SHA256_PATTERN.test(value))
|
|
126
|
+
throw invalidPayload(field);
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function nullableSha256(source, field) {
|
|
130
|
+
return source[field] === null ? null : sha256(source, field);
|
|
131
|
+
}
|
|
132
|
+
function literal(source, field, values) {
|
|
133
|
+
const value = source[field];
|
|
134
|
+
if (typeof value !== 'string' || !values.includes(value))
|
|
135
|
+
throw invalidPayload(field);
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function authority(value, field) {
|
|
139
|
+
const source = exactRecord(value, field, ['generation', 'kind']);
|
|
140
|
+
return {
|
|
141
|
+
generation: positiveInteger(source, 'generation'),
|
|
142
|
+
kind: literal(source, 'kind', ['cloud', 'lan']),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function canonicalBase64urlPublicKey(source, field) {
|
|
146
|
+
const value = boundedString(source, field, 128);
|
|
147
|
+
if (!BASE64URL_PATTERN.test(value))
|
|
148
|
+
throw invalidPayload(field);
|
|
149
|
+
const remainder = value.length % 4;
|
|
150
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
151
|
+
const finalIndex = alphabet.indexOf(value[value.length - 1]);
|
|
152
|
+
const decodedBytes = Math.floor(value.length * 6 / 8);
|
|
153
|
+
if (remainder === 1
|
|
154
|
+
|| (remainder === 2 && (finalIndex & 15) !== 0)
|
|
155
|
+
|| (remainder === 3 && (finalIndex & 3) !== 0)
|
|
156
|
+
|| decodedBytes !== 32)
|
|
157
|
+
throw invalidPayload(field);
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
function proofValue(source, field) {
|
|
161
|
+
const value = boundedString(source, field, 4096);
|
|
162
|
+
if (!BASE64URL_PATTERN.test(value))
|
|
163
|
+
throw invalidPayload(field);
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
function absoluteTargetUrl(source) {
|
|
167
|
+
const targetUrl = boundedString(source, 'targetUrl', 2048);
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = new URL(targetUrl);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
throw invalidPayload('targetUrl');
|
|
174
|
+
}
|
|
175
|
+
if ((parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
176
|
+
|| parsed.username.length > 0
|
|
177
|
+
|| parsed.password.length > 0
|
|
178
|
+
|| targetUrl.includes('?')
|
|
179
|
+
|| targetUrl.includes('#'))
|
|
180
|
+
throw invalidPayload('targetUrl');
|
|
181
|
+
return targetUrl;
|
|
182
|
+
}
|
|
183
|
+
function principal(source, field) {
|
|
184
|
+
return token(source, field, value => (typeof value === 'string' && PRINCIPAL_PATTERN.test(value)));
|
|
185
|
+
}
|
|
186
|
+
function sanitizeBackupBasePrincipalRecord(item) {
|
|
187
|
+
const source = record(item, 'record');
|
|
188
|
+
const value = record(source.value, 'value');
|
|
189
|
+
if (source.kind === 'principal-binding') {
|
|
190
|
+
return {
|
|
191
|
+
...source,
|
|
192
|
+
value: { ...value, principalId: `principal_${String(value.memberId)}` },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (source.kind === 'terminal-responder' && Array.isArray(value.acknowledgements)) {
|
|
196
|
+
return {
|
|
197
|
+
...source,
|
|
198
|
+
value: {
|
|
199
|
+
...value,
|
|
200
|
+
acknowledgements: value.acknowledgements.map(item => ({
|
|
201
|
+
...record(item, 'acknowledgement'),
|
|
202
|
+
principalId: `principal_${String(record(item, 'acknowledgement').memberId)}`,
|
|
203
|
+
})),
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return item;
|
|
208
|
+
}
|
|
209
|
+
function restoreBackupBasePrincipals(source, base) {
|
|
210
|
+
if (base.kind === 'principal-binding') {
|
|
211
|
+
const value = exactRecord(source.value, 'value', [
|
|
212
|
+
'boundAt', 'memberId', 'principalId', 'projectId',
|
|
213
|
+
]);
|
|
214
|
+
return {
|
|
215
|
+
...base,
|
|
216
|
+
value: { ...base.value, principalId: principal(value, 'principalId') },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (base.kind === 'terminal-responder') {
|
|
220
|
+
const value = exactRecord(source.value, 'value', [
|
|
221
|
+
'acknowledgements',
|
|
222
|
+
'eligibleMemberIds',
|
|
223
|
+
'expiresAt',
|
|
224
|
+
'operation',
|
|
225
|
+
'operationId',
|
|
226
|
+
'projectId',
|
|
227
|
+
'responseJson',
|
|
228
|
+
]);
|
|
229
|
+
const sourceAcknowledgements = value.acknowledgements;
|
|
230
|
+
if (!Array.isArray(sourceAcknowledgements)
|
|
231
|
+
|| sourceAcknowledgements.length !== base.value.acknowledgements.length)
|
|
232
|
+
throw invalidPayload('acknowledgements');
|
|
233
|
+
const acknowledgements = base.value.acknowledgements.map((item, index) => {
|
|
234
|
+
const original = exactRecord(sourceAcknowledgements[index], 'acknowledgement', [
|
|
235
|
+
'acknowledgedAt', 'memberId', 'principalId',
|
|
236
|
+
]);
|
|
237
|
+
return { ...item, principalId: principal(original, 'principalId') };
|
|
238
|
+
});
|
|
239
|
+
if (new Set(acknowledgements.map(item => item.principalId)).size !== acknowledgements.length) {
|
|
240
|
+
throw invalidPayload('acknowledgements');
|
|
241
|
+
}
|
|
242
|
+
return { ...base, value: { ...base.value, acknowledgements } };
|
|
243
|
+
}
|
|
244
|
+
return base;
|
|
245
|
+
}
|
|
246
|
+
function repositoryPublicationRef(value) {
|
|
247
|
+
const source = exactRecord(value, 'ref', ['name', 'oid']);
|
|
248
|
+
const name = boundedString(source, 'name', 512);
|
|
249
|
+
const memberId = name.startsWith(CollabConstants_1.COLLAB_MEMBER_REF_PREFIX)
|
|
250
|
+
? name.slice(CollabConstants_1.COLLAB_MEMBER_REF_PREFIX.length)
|
|
251
|
+
: undefined;
|
|
252
|
+
if (name !== CollabConstants_1.COLLAB_MAIN_REF
|
|
253
|
+
&& (memberId === undefined || !(0, CollabValidation_1.isCollabMemberId)(memberId))) {
|
|
254
|
+
throw invalidPayload('name');
|
|
255
|
+
}
|
|
256
|
+
return { name, oid: token(source, 'oid', CollabValidation_1.isCollabGitOid) };
|
|
257
|
+
}
|
|
258
|
+
function inactiveRepositoryPublication(value) {
|
|
259
|
+
if (value === null)
|
|
260
|
+
return null;
|
|
261
|
+
const source = exactRecord(value, 'inactivePublication', [
|
|
262
|
+
'artifactKey',
|
|
263
|
+
'bundleByteCount',
|
|
264
|
+
'bundleSha256',
|
|
265
|
+
'objectFormat',
|
|
266
|
+
'operationId',
|
|
267
|
+
'placementGeneration',
|
|
268
|
+
'projectId',
|
|
269
|
+
'publicationMarkerSha256',
|
|
270
|
+
'refs',
|
|
271
|
+
'repositoryStorageKey',
|
|
272
|
+
'status',
|
|
273
|
+
'storageNodeId',
|
|
274
|
+
'validationMarkerSha256',
|
|
275
|
+
]);
|
|
276
|
+
if (!Array.isArray(source.refs) || source.refs.length < 2) {
|
|
277
|
+
throw invalidPayload('refs');
|
|
278
|
+
}
|
|
279
|
+
const refs = source.refs.map(repositoryPublicationRef);
|
|
280
|
+
const objectFormat = literal(source, 'objectFormat', ['sha1', 'sha256']);
|
|
281
|
+
const objectLength = objectFormat === 'sha1' ? 40 : 64;
|
|
282
|
+
const bundleByteCount = positiveInteger(source, 'bundleByteCount');
|
|
283
|
+
const repositoryStorageKey = boundedString(source, 'repositoryStorageKey', 128);
|
|
284
|
+
const storageNodeId = boundedString(source, 'storageNodeId', 64);
|
|
285
|
+
if (refs[0].name !== CollabConstants_1.COLLAB_MAIN_REF
|
|
286
|
+
|| refs.slice(1).some(item => !item.name.startsWith(CollabConstants_1.COLLAB_MEMBER_REF_PREFIX))
|
|
287
|
+
|| refs.some(item => item.oid.length !== objectLength)
|
|
288
|
+
|| refs.some((item, index) => index > 0
|
|
289
|
+
&& refs[index - 1].name.localeCompare(item.name, 'en-US') >= 0)
|
|
290
|
+
|| bundleByteCount > CollabProjectCheckpoint_1.COLLAB_CHECKPOINT_ARTIFACT_LIMITS.maxRepositoryBundleBytes
|
|
291
|
+
|| !STORAGE_KEY_PATTERN.test(repositoryStorageKey)
|
|
292
|
+
|| !STORAGE_NODE_PATTERN.test(storageNodeId))
|
|
293
|
+
throw invalidPayload('refs');
|
|
294
|
+
const artifactKey = boundedString(source, 'artifactKey', 64);
|
|
295
|
+
const placementGeneration = positiveInteger(source, 'placementGeneration');
|
|
296
|
+
if (!SHA256_PATTERN.test(artifactKey) || placementGeneration !== 1) {
|
|
297
|
+
throw invalidPayload('inactivePublication');
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
artifactKey,
|
|
301
|
+
bundleByteCount,
|
|
302
|
+
bundleSha256: sha256(source, 'bundleSha256'),
|
|
303
|
+
objectFormat,
|
|
304
|
+
operationId: token(source, 'operationId'),
|
|
305
|
+
placementGeneration,
|
|
306
|
+
projectId: token(source, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
307
|
+
publicationMarkerSha256: sha256(source, 'publicationMarkerSha256'),
|
|
308
|
+
refs: Object.freeze(refs),
|
|
309
|
+
repositoryStorageKey,
|
|
310
|
+
status: literal(source, 'status', ['inactive']),
|
|
311
|
+
storageNodeId,
|
|
312
|
+
validationMarkerSha256: sha256(source, 'validationMarkerSha256'),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function lanToCloudSourceEvidence(value) {
|
|
316
|
+
if (value === null)
|
|
317
|
+
return null;
|
|
318
|
+
const source = exactRecord(value, 'sourceEvidence', [
|
|
319
|
+
'checkpointManifestSha256',
|
|
320
|
+
'principalId',
|
|
321
|
+
'proof',
|
|
322
|
+
'receiptKeyId',
|
|
323
|
+
'receiptPublicKey',
|
|
324
|
+
'schemaVersion',
|
|
325
|
+
]);
|
|
326
|
+
if (source.schemaVersion !== 1)
|
|
327
|
+
throw invalidPayload('schemaVersion');
|
|
328
|
+
return {
|
|
329
|
+
checkpointManifestSha256: sha256(source, 'checkpointManifestSha256'),
|
|
330
|
+
principalId: principal(source, 'principalId'),
|
|
331
|
+
proof: proofValue(source, 'proof'),
|
|
332
|
+
receiptKeyId: token(source, 'receiptKeyId'),
|
|
333
|
+
receiptPublicKey: canonicalBase64urlPublicKey(source, 'receiptPublicKey'),
|
|
334
|
+
schemaVersion: 1,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function cloudToLanTargetEvidence(value) {
|
|
338
|
+
if (value === null)
|
|
339
|
+
return null;
|
|
340
|
+
const source = exactRecord(value, 'targetEvidence', [
|
|
341
|
+
'acceptanceIntentId',
|
|
342
|
+
'principalId',
|
|
343
|
+
'proof',
|
|
344
|
+
'receiptKeyId',
|
|
345
|
+
'receiptPublicKey',
|
|
346
|
+
'schemaVersion',
|
|
347
|
+
]);
|
|
348
|
+
if (source.schemaVersion !== 1)
|
|
349
|
+
throw invalidPayload('schemaVersion');
|
|
350
|
+
return {
|
|
351
|
+
acceptanceIntentId: token(source, 'acceptanceIntentId'),
|
|
352
|
+
principalId: principal(source, 'principalId'),
|
|
353
|
+
proof: proofValue(source, 'proof'),
|
|
354
|
+
receiptKeyId: token(source, 'receiptKeyId'),
|
|
355
|
+
receiptPublicKey: canonicalBase64urlPublicKey(source, 'receiptPublicKey'),
|
|
356
|
+
schemaVersion: 1,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function operation(source, field) {
|
|
360
|
+
const value = source[field];
|
|
361
|
+
if (typeof value !== 'string')
|
|
362
|
+
throw invalidPayload(field);
|
|
363
|
+
try {
|
|
364
|
+
(0, CollabControlOperationCodecs_1.collabControlOperationCodec)(value);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
throw invalidPayload(field);
|
|
368
|
+
}
|
|
369
|
+
return value;
|
|
370
|
+
}
|
|
371
|
+
function recordEnvelope(value) {
|
|
372
|
+
const source = exactRecord(value, 'record', ['kind', 'recordId', 'revision', 'value']);
|
|
373
|
+
const kind = source.kind;
|
|
374
|
+
if (typeof kind !== 'string' || !BACKUP_KIND_SET.has(kind))
|
|
375
|
+
throw invalidPayload('kind');
|
|
376
|
+
return {
|
|
377
|
+
kind: kind,
|
|
378
|
+
recordId: boundedString(source, 'recordId', 512),
|
|
379
|
+
revision: positiveInteger(source, 'revision'),
|
|
380
|
+
source,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function collabProjectBackupIdempotencyRecordId(input) {
|
|
384
|
+
if (!(0, CollabValidation_1.isCollabProjectId)(input.projectId)
|
|
385
|
+
|| !(0, CollabValidation_1.isCollabMemberId)(input.memberId)
|
|
386
|
+
|| !(0, CollabValidation_1.isCollabOpaqueId)(input.idempotencyKey))
|
|
387
|
+
throw invalidPayload('idempotencyIdentity');
|
|
388
|
+
operation({ operation: input.operation }, 'operation');
|
|
389
|
+
return `${input.projectId}:${input.memberId}:${input.operation}:${input.idempotencyKey}`;
|
|
390
|
+
}
|
|
391
|
+
function idempotencyResultRecord(source, recordId, revision) {
|
|
392
|
+
const value = exactRecord(source.value, 'value', [
|
|
393
|
+
'createdAt',
|
|
394
|
+
'idempotencyKey',
|
|
395
|
+
'memberId',
|
|
396
|
+
'operation',
|
|
397
|
+
'projectId',
|
|
398
|
+
'requestFingerprint',
|
|
399
|
+
'responseJson',
|
|
400
|
+
]);
|
|
401
|
+
const decodedOperation = operation(value, 'operation');
|
|
402
|
+
if (PLAINTEXT_CLAIM_RESPONSE_OPERATION_SET.has(decodedOperation)) {
|
|
403
|
+
throw invalidPayload('responseJson');
|
|
404
|
+
}
|
|
405
|
+
const projectId = token(value, 'projectId', CollabValidation_1.isCollabProjectId);
|
|
406
|
+
const memberId = token(value, 'memberId', CollabValidation_1.isCollabMemberId);
|
|
407
|
+
const idempotencyKey = token(value, 'idempotencyKey');
|
|
408
|
+
let responseJson;
|
|
409
|
+
try {
|
|
410
|
+
responseJson = boundedString(value, 'responseJson', 512 * 1024, true);
|
|
411
|
+
const decoded = (0, CollabControlOperationCodecs_1.collabControlOperationCodec)(decodedOperation).decodeResponse(JSON.parse(responseJson));
|
|
412
|
+
if (JSON.stringify(decoded) !== responseJson
|
|
413
|
+
|| (typeof decoded === 'object'
|
|
414
|
+
&& decoded !== null
|
|
415
|
+
&& 'projectId' in decoded
|
|
416
|
+
&& decoded.projectId !== projectId))
|
|
417
|
+
throw invalidPayload('responseJson');
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
throw invalidPayload('responseJson');
|
|
421
|
+
}
|
|
422
|
+
if (recordId !== collabProjectBackupIdempotencyRecordId({
|
|
423
|
+
idempotencyKey,
|
|
424
|
+
memberId,
|
|
425
|
+
operation: decodedOperation,
|
|
426
|
+
projectId,
|
|
427
|
+
}))
|
|
428
|
+
throw invalidPayload('recordId');
|
|
429
|
+
return {
|
|
430
|
+
kind: 'idempotency-result',
|
|
431
|
+
recordId,
|
|
432
|
+
revision,
|
|
433
|
+
value: {
|
|
434
|
+
createdAt: timestamp(value, 'createdAt'),
|
|
435
|
+
idempotencyKey,
|
|
436
|
+
memberId,
|
|
437
|
+
operation: decodedOperation,
|
|
438
|
+
projectId,
|
|
439
|
+
requestFingerprint: sha256(value, 'requestFingerprint'),
|
|
440
|
+
responseJson,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function lifecycleJournalRecord(source, recordId, revision) {
|
|
445
|
+
const value = exactRecord(source.value, 'value', [
|
|
446
|
+
'actorMemberId',
|
|
447
|
+
'batchRevision',
|
|
448
|
+
'batchSha256',
|
|
449
|
+
'checkpointSha256',
|
|
450
|
+
'createdAt',
|
|
451
|
+
'direction',
|
|
452
|
+
'expectedAuthorityGeneration',
|
|
453
|
+
'expectedPersonalRefOid',
|
|
454
|
+
'idempotencyKey',
|
|
455
|
+
'operationId',
|
|
456
|
+
'operationKind',
|
|
457
|
+
'phase',
|
|
458
|
+
'projectId',
|
|
459
|
+
'recoveryFromPhase',
|
|
460
|
+
'requestFingerprint',
|
|
461
|
+
'resultSha256',
|
|
462
|
+
'scheduledAt',
|
|
463
|
+
'state',
|
|
464
|
+
'updatedAt',
|
|
465
|
+
]);
|
|
466
|
+
const operationId = token(value, 'operationId');
|
|
467
|
+
if (recordId !== operationId)
|
|
468
|
+
throw invalidPayload('recordId');
|
|
469
|
+
const operationKind = literal(value, 'operationKind', [
|
|
470
|
+
'authority-transfer', 'backup', 'delete', 'export', 'leave', 'retire',
|
|
471
|
+
]);
|
|
472
|
+
const direction = value.direction === null
|
|
473
|
+
? null
|
|
474
|
+
: literal(value, 'direction', ['cloud-to-lan', 'lan-to-cloud']);
|
|
475
|
+
if ((operationKind === 'authority-transfer') !== (direction !== null)) {
|
|
476
|
+
throw invalidPayload('direction');
|
|
477
|
+
}
|
|
478
|
+
const expectedPersonalRefOid = value.expectedPersonalRefOid === null
|
|
479
|
+
? null
|
|
480
|
+
: token(value, 'expectedPersonalRefOid', CollabValidation_1.isCollabGitOid);
|
|
481
|
+
if ((operationKind === 'leave') !== (expectedPersonalRefOid !== null)) {
|
|
482
|
+
throw invalidPayload('expectedPersonalRefOid');
|
|
483
|
+
}
|
|
484
|
+
const batchRevision = nullablePositiveInteger(value, 'batchRevision');
|
|
485
|
+
const batchSha256 = nullableSha256(value, 'batchSha256');
|
|
486
|
+
const checkpointSha256 = nullableSha256(value, 'checkpointSha256');
|
|
487
|
+
if ((batchRevision === null) !== (batchSha256 === null)
|
|
488
|
+
|| (batchRevision !== null && checkpointSha256 === null))
|
|
489
|
+
throw invalidPayload('batchRevision');
|
|
490
|
+
const state = literal(value, 'state', [
|
|
491
|
+
'active', 'cancelled', 'completed', 'recovery-required',
|
|
492
|
+
]);
|
|
493
|
+
const recoveryFromPhase = value.recoveryFromPhase === null
|
|
494
|
+
? null
|
|
495
|
+
: boundedString(value, 'recoveryFromPhase', 64);
|
|
496
|
+
if ((state === 'recovery-required') !== (recoveryFromPhase !== null)
|
|
497
|
+
|| (recoveryFromPhase !== null && !PHASE_PATTERN.test(recoveryFromPhase)))
|
|
498
|
+
throw invalidPayload('recoveryFromPhase');
|
|
499
|
+
const phase = boundedString(value, 'phase', 64);
|
|
500
|
+
if (!PHASE_PATTERN.test(phase))
|
|
501
|
+
throw invalidPayload('phase');
|
|
502
|
+
const createdAt = timestamp(value, 'createdAt');
|
|
503
|
+
const scheduledAt = timestamp(value, 'scheduledAt');
|
|
504
|
+
const updatedAt = timestamp(value, 'updatedAt');
|
|
505
|
+
if (Date.parse(scheduledAt) < Date.parse(createdAt)
|
|
506
|
+
|| Date.parse(updatedAt) < Date.parse(createdAt))
|
|
507
|
+
throw invalidPayload('updatedAt');
|
|
508
|
+
return {
|
|
509
|
+
kind: 'lifecycle-journal',
|
|
510
|
+
recordId,
|
|
511
|
+
revision,
|
|
512
|
+
value: {
|
|
513
|
+
actorMemberId: nullableToken(value, 'actorMemberId', CollabValidation_1.isCollabMemberId),
|
|
514
|
+
batchRevision,
|
|
515
|
+
batchSha256,
|
|
516
|
+
checkpointSha256,
|
|
517
|
+
createdAt,
|
|
518
|
+
direction,
|
|
519
|
+
expectedAuthorityGeneration: positiveInteger(value, 'expectedAuthorityGeneration'),
|
|
520
|
+
expectedPersonalRefOid,
|
|
521
|
+
idempotencyKey: token(value, 'idempotencyKey'),
|
|
522
|
+
operationId,
|
|
523
|
+
operationKind,
|
|
524
|
+
phase,
|
|
525
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
526
|
+
recoveryFromPhase,
|
|
527
|
+
requestFingerprint: sha256(value, 'requestFingerprint'),
|
|
528
|
+
resultSha256: nullableSha256(value, 'resultSha256'),
|
|
529
|
+
scheduledAt,
|
|
530
|
+
state,
|
|
531
|
+
updatedAt,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function nullableProof(source, field) {
|
|
536
|
+
return source[field] === null ? null : proofValue(source, field);
|
|
537
|
+
}
|
|
538
|
+
function authorityTransferRecoveryRecord(source, recordId, revision) {
|
|
539
|
+
const value = exactRecord(source.value, 'value', [
|
|
540
|
+
'cancellationRequestSha256',
|
|
541
|
+
'createdAt',
|
|
542
|
+
'expiresAt',
|
|
543
|
+
'inactivePublication',
|
|
544
|
+
'projectId',
|
|
545
|
+
'relinquishmentProof',
|
|
546
|
+
'sourceAuthority',
|
|
547
|
+
'sourceHostMemberId',
|
|
548
|
+
'sourceEvidence',
|
|
549
|
+
'sourceReopenSha256',
|
|
550
|
+
'stageSha256',
|
|
551
|
+
'targetActivationProof',
|
|
552
|
+
'targetActivationRequestSha256',
|
|
553
|
+
'targetAuthority',
|
|
554
|
+
'targetHostMemberId',
|
|
555
|
+
'targetEvidence',
|
|
556
|
+
'targetUrl',
|
|
557
|
+
'transferId',
|
|
558
|
+
'updatedAt',
|
|
559
|
+
]);
|
|
560
|
+
const transferId = token(value, 'transferId');
|
|
561
|
+
if (recordId !== transferId)
|
|
562
|
+
throw invalidPayload('recordId');
|
|
563
|
+
const projectId = token(value, 'projectId', CollabValidation_1.isCollabProjectId);
|
|
564
|
+
const sourceAuthority = authority(value.sourceAuthority, 'sourceAuthority');
|
|
565
|
+
const targetAuthority = authority(value.targetAuthority, 'targetAuthority');
|
|
566
|
+
const sourceHostMemberId = nullableToken(value, 'sourceHostMemberId', CollabValidation_1.isCollabMemberId);
|
|
567
|
+
const targetHostMemberId = nullableToken(value, 'targetHostMemberId', CollabValidation_1.isCollabMemberId);
|
|
568
|
+
const inactivePublication = inactiveRepositoryPublication(value.inactivePublication);
|
|
569
|
+
const sourceEvidence = lanToCloudSourceEvidence(value.sourceEvidence);
|
|
570
|
+
const targetEvidence = cloudToLanTargetEvidence(value.targetEvidence);
|
|
571
|
+
if (sourceAuthority.kind === targetAuthority.kind
|
|
572
|
+
|| targetAuthority.generation !== sourceAuthority.generation + 1
|
|
573
|
+
|| (sourceAuthority.kind === 'lan'
|
|
574
|
+
? sourceHostMemberId === null || targetHostMemberId !== null
|
|
575
|
+
: sourceHostMemberId !== null || targetHostMemberId === null)
|
|
576
|
+
|| (sourceAuthority.kind === 'lan'
|
|
577
|
+
? targetEvidence !== null
|
|
578
|
+
: sourceEvidence !== null || inactivePublication !== null))
|
|
579
|
+
throw invalidPayload('sourceAuthority');
|
|
580
|
+
if (inactivePublication !== null && (inactivePublication.projectId !== projectId
|
|
581
|
+
|| inactivePublication.operationId !== transferId))
|
|
582
|
+
throw invalidPayload('inactivePublication');
|
|
583
|
+
const relinquishmentProof = value.relinquishmentProof === null
|
|
584
|
+
? null
|
|
585
|
+
: (0, CollabAuthorityTransfer_1.decodeCollabAuthorityRelinquishmentProof)(value.relinquishmentProof);
|
|
586
|
+
if (relinquishmentProof !== null && (relinquishmentProof.projectId !== projectId
|
|
587
|
+
|| relinquishmentProof.transferId !== transferId
|
|
588
|
+
|| relinquishmentProof.sourceAuthority.kind !== sourceAuthority.kind
|
|
589
|
+
|| relinquishmentProof.sourceAuthority.generation !== sourceAuthority.generation
|
|
590
|
+
|| relinquishmentProof.sourceHostMemberId !== sourceHostMemberId
|
|
591
|
+
|| relinquishmentProof.targetAuthority.kind !== targetAuthority.kind
|
|
592
|
+
|| relinquishmentProof.targetAuthority.generation !== targetAuthority.generation))
|
|
593
|
+
throw invalidPayload('relinquishmentProof');
|
|
594
|
+
const createdAt = timestamp(value, 'createdAt');
|
|
595
|
+
const expiresAt = timestamp(value, 'expiresAt');
|
|
596
|
+
const updatedAt = timestamp(value, 'updatedAt');
|
|
597
|
+
if (Date.parse(expiresAt) <= Date.parse(createdAt)
|
|
598
|
+
|| Date.parse(updatedAt) < Date.parse(createdAt))
|
|
599
|
+
throw invalidPayload('updatedAt');
|
|
600
|
+
return {
|
|
601
|
+
kind: 'authority-transfer-recovery',
|
|
602
|
+
recordId,
|
|
603
|
+
revision,
|
|
604
|
+
value: {
|
|
605
|
+
cancellationRequestSha256: nullableSha256(value, 'cancellationRequestSha256'),
|
|
606
|
+
createdAt,
|
|
607
|
+
expiresAt,
|
|
608
|
+
inactivePublication,
|
|
609
|
+
projectId,
|
|
610
|
+
relinquishmentProof,
|
|
611
|
+
sourceAuthority,
|
|
612
|
+
sourceHostMemberId,
|
|
613
|
+
sourceEvidence,
|
|
614
|
+
sourceReopenSha256: nullableSha256(value, 'sourceReopenSha256'),
|
|
615
|
+
stageSha256: nullableSha256(value, 'stageSha256'),
|
|
616
|
+
targetActivationProof: nullableProof(value, 'targetActivationProof'),
|
|
617
|
+
targetActivationRequestSha256: nullableSha256(value, 'targetActivationRequestSha256'),
|
|
618
|
+
targetAuthority,
|
|
619
|
+
targetHostMemberId,
|
|
620
|
+
targetEvidence,
|
|
621
|
+
targetUrl: absoluteTargetUrl(value),
|
|
622
|
+
transferId,
|
|
623
|
+
updatedAt,
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function transferredMembershipClaimRecord(source, recordId, revision) {
|
|
628
|
+
const value = exactRecord(source.value, 'value', [
|
|
629
|
+
'batchRevision',
|
|
630
|
+
'checkpointSha256',
|
|
631
|
+
'claimSha256',
|
|
632
|
+
'createdAt',
|
|
633
|
+
'expiresAt',
|
|
634
|
+
'memberId',
|
|
635
|
+
'operationIntentId',
|
|
636
|
+
'projectId',
|
|
637
|
+
'redemptionReceiptId',
|
|
638
|
+
'state',
|
|
639
|
+
'targetPrincipalId',
|
|
640
|
+
'transferId',
|
|
641
|
+
'updatedAt',
|
|
642
|
+
]);
|
|
643
|
+
const transferId = token(value, 'transferId');
|
|
644
|
+
const memberId = token(value, 'memberId', CollabValidation_1.isCollabMemberId);
|
|
645
|
+
if (recordId !== `${transferId}:${memberId}`)
|
|
646
|
+
throw invalidPayload('recordId');
|
|
647
|
+
const state = literal(value, 'state', ['redeemed', 'revoked', 'unclaimed']);
|
|
648
|
+
const targetPrincipalId = nullableToken(value, 'targetPrincipalId', value => (typeof value === 'string' && PRINCIPAL_PATTERN.test(value)));
|
|
649
|
+
const operationIntentId = nullableToken(value, 'operationIntentId');
|
|
650
|
+
const redemptionReceiptId = nullableToken(value, 'redemptionReceiptId');
|
|
651
|
+
if ((state !== 'redeemed'
|
|
652
|
+
&& (targetPrincipalId !== null
|
|
653
|
+
|| operationIntentId !== null
|
|
654
|
+
|| redemptionReceiptId !== null))
|
|
655
|
+
|| (state === 'redeemed'
|
|
656
|
+
&& (targetPrincipalId === null
|
|
657
|
+
|| operationIntentId === null
|
|
658
|
+
|| redemptionReceiptId === null)))
|
|
659
|
+
throw invalidPayload('state');
|
|
660
|
+
const createdAt = timestamp(value, 'createdAt');
|
|
661
|
+
const expiresAt = timestamp(value, 'expiresAt');
|
|
662
|
+
const updatedAt = timestamp(value, 'updatedAt');
|
|
663
|
+
if (Date.parse(expiresAt) <= Date.parse(createdAt)
|
|
664
|
+
|| Date.parse(updatedAt) < Date.parse(createdAt))
|
|
665
|
+
throw invalidPayload('updatedAt');
|
|
666
|
+
return {
|
|
667
|
+
kind: 'transferred-membership-claim',
|
|
668
|
+
recordId,
|
|
669
|
+
revision,
|
|
670
|
+
value: {
|
|
671
|
+
batchRevision: positiveInteger(value, 'batchRevision'),
|
|
672
|
+
checkpointSha256: sha256(value, 'checkpointSha256'),
|
|
673
|
+
claimSha256: sha256(value, 'claimSha256'),
|
|
674
|
+
createdAt,
|
|
675
|
+
expiresAt,
|
|
676
|
+
memberId,
|
|
677
|
+
operationIntentId,
|
|
678
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
679
|
+
redemptionReceiptId,
|
|
680
|
+
state,
|
|
681
|
+
targetPrincipalId,
|
|
682
|
+
transferId,
|
|
683
|
+
updatedAt,
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function transferReceiptKeyRecord(source, recordId, revision) {
|
|
688
|
+
const value = exactRecord(source.value, 'value', [
|
|
689
|
+
'createdAt',
|
|
690
|
+
'projectId',
|
|
691
|
+
'receiptKeyId',
|
|
692
|
+
'receiptPublicKey',
|
|
693
|
+
'receiptPublicKeyEncoding',
|
|
694
|
+
'signatureAlgorithm',
|
|
695
|
+
'transferId',
|
|
696
|
+
]);
|
|
697
|
+
const transferId = token(value, 'transferId');
|
|
698
|
+
const receiptKeyId = token(value, 'receiptKeyId');
|
|
699
|
+
if (recordId !== `${transferId}:${receiptKeyId}`)
|
|
700
|
+
throw invalidPayload('recordId');
|
|
701
|
+
return {
|
|
702
|
+
kind: 'transfer-receipt-key',
|
|
703
|
+
recordId,
|
|
704
|
+
revision,
|
|
705
|
+
value: {
|
|
706
|
+
createdAt: timestamp(value, 'createdAt'),
|
|
707
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
708
|
+
receiptKeyId,
|
|
709
|
+
receiptPublicKey: canonicalBase64urlPublicKey(value, 'receiptPublicKey'),
|
|
710
|
+
receiptPublicKeyEncoding: literal(value, 'receiptPublicKeyEncoding', ['base64url-raw']),
|
|
711
|
+
signatureAlgorithm: literal(value, 'signatureAlgorithm', ['ed25519']),
|
|
712
|
+
transferId,
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function transferClaimBatchReceiptRecord(source, recordId, revision) {
|
|
717
|
+
const value = exactRecord(source.value, 'value', ['receipt']);
|
|
718
|
+
const receipt = (0, CollabAuthorityTransfer_1.decodeCollabTransferredMembershipClaimCustodyReceipt)(value.receipt);
|
|
719
|
+
if (recordId !== receipt.transferId)
|
|
720
|
+
throw invalidPayload('recordId');
|
|
721
|
+
return { kind: 'transfer-claim-batch-receipt', recordId, revision, value: { receipt } };
|
|
722
|
+
}
|
|
723
|
+
function transferRedemptionReceiptRecord(source, recordId, revision) {
|
|
724
|
+
const value = exactRecord(source.value, 'value', [
|
|
725
|
+
'acknowledgedAt',
|
|
726
|
+
'projectId',
|
|
727
|
+
'receipt',
|
|
728
|
+
]);
|
|
729
|
+
const receipt = (0, CollabAuthorityTransfer_1.decodeCollabTransferredMembershipRedemptionReceipt)(value.receipt);
|
|
730
|
+
const projectId = token(value, 'projectId', CollabValidation_1.isCollabProjectId);
|
|
731
|
+
if (projectId !== receipt.projectId
|
|
732
|
+
|| recordId !== `${receipt.transferId}:${receipt.memberId}`)
|
|
733
|
+
throw invalidPayload('recordId');
|
|
734
|
+
const acknowledgedAt = nullableTimestamp(value, 'acknowledgedAt');
|
|
735
|
+
if (acknowledgedAt !== null
|
|
736
|
+
&& Date.parse(acknowledgedAt) < Date.parse(receipt.redeemedAt))
|
|
737
|
+
throw invalidPayload('acknowledgedAt');
|
|
738
|
+
return {
|
|
739
|
+
kind: 'transfer-redemption-receipt',
|
|
740
|
+
recordId,
|
|
741
|
+
revision,
|
|
742
|
+
value: { acknowledgedAt, projectId, receipt },
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function terminalPrincipalRecord(source, recordId, revision) {
|
|
746
|
+
const value = exactRecord(source.value, 'value', [
|
|
747
|
+
'acknowledgedAt',
|
|
748
|
+
'memberId',
|
|
749
|
+
'operationId',
|
|
750
|
+
'operationKind',
|
|
751
|
+
'principalId',
|
|
752
|
+
'projectId',
|
|
753
|
+
]);
|
|
754
|
+
const operationId = token(value, 'operationId');
|
|
755
|
+
const memberId = token(value, 'memberId', CollabValidation_1.isCollabMemberId);
|
|
756
|
+
if (recordId !== `${operationId}:${memberId}`)
|
|
757
|
+
throw invalidPayload('recordId');
|
|
758
|
+
return {
|
|
759
|
+
kind: 'terminal-principal',
|
|
760
|
+
recordId,
|
|
761
|
+
revision,
|
|
762
|
+
value: {
|
|
763
|
+
acknowledgedAt: nullableTimestamp(value, 'acknowledgedAt'),
|
|
764
|
+
memberId,
|
|
765
|
+
operationId,
|
|
766
|
+
operationKind: literal(value, 'operationKind', ['authority-transfer', 'retire']),
|
|
767
|
+
principalId: principal(value, 'principalId'),
|
|
768
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function terminalResponderReplayRecord(source, recordId, revision) {
|
|
773
|
+
const value = exactRecord(source.value, 'value', [
|
|
774
|
+
'memberId', 'operationId', 'projectId', 'requestSha256',
|
|
775
|
+
]);
|
|
776
|
+
const operationId = token(value, 'operationId');
|
|
777
|
+
if (recordId !== operationId)
|
|
778
|
+
throw invalidPayload('recordId');
|
|
779
|
+
return {
|
|
780
|
+
kind: 'terminal-responder-replay',
|
|
781
|
+
recordId,
|
|
782
|
+
revision,
|
|
783
|
+
value: {
|
|
784
|
+
memberId: token(value, 'memberId', CollabValidation_1.isCollabMemberId),
|
|
785
|
+
operationId,
|
|
786
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
787
|
+
requestSha256: sha256(value, 'requestSha256'),
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function leaveFormerPrincipalReplayRecord(source, recordId, revision) {
|
|
792
|
+
const value = exactRecord(source.value, 'value', [
|
|
793
|
+
'completedAt',
|
|
794
|
+
'createdAt',
|
|
795
|
+
'expectedPersonalRefOid',
|
|
796
|
+
'expiresAt',
|
|
797
|
+
'intentId',
|
|
798
|
+
'memberId',
|
|
799
|
+
'operationId',
|
|
800
|
+
'principalSha256',
|
|
801
|
+
'projectId',
|
|
802
|
+
'requestFingerprint',
|
|
803
|
+
'resultSha256',
|
|
804
|
+
'state',
|
|
805
|
+
]);
|
|
806
|
+
const operationId = token(value, 'operationId');
|
|
807
|
+
if (recordId !== operationId)
|
|
808
|
+
throw invalidPayload('recordId');
|
|
809
|
+
const state = literal(value, 'state', ['completed', 'recovering']);
|
|
810
|
+
const completedAt = nullableTimestamp(value, 'completedAt');
|
|
811
|
+
const resultSha256 = nullableSha256(value, 'resultSha256');
|
|
812
|
+
if ((state === 'completed' && (completedAt === null || resultSha256 === null))
|
|
813
|
+
|| (state === 'recovering' && (completedAt !== null || resultSha256 !== null))) {
|
|
814
|
+
throw invalidPayload('state');
|
|
815
|
+
}
|
|
816
|
+
const createdAt = timestamp(value, 'createdAt');
|
|
817
|
+
const expiresAt = timestamp(value, 'expiresAt');
|
|
818
|
+
if (Date.parse(expiresAt) <= Date.parse(createdAt)
|
|
819
|
+
|| (completedAt !== null && Date.parse(completedAt) < Date.parse(createdAt)))
|
|
820
|
+
throw invalidPayload('completedAt');
|
|
821
|
+
return {
|
|
822
|
+
kind: 'leave-former-principal-replay',
|
|
823
|
+
recordId,
|
|
824
|
+
revision,
|
|
825
|
+
value: {
|
|
826
|
+
completedAt,
|
|
827
|
+
createdAt,
|
|
828
|
+
expectedPersonalRefOid: token(value, 'expectedPersonalRefOid', CollabValidation_1.isCollabGitOid),
|
|
829
|
+
expiresAt,
|
|
830
|
+
intentId: token(value, 'intentId'),
|
|
831
|
+
memberId: token(value, 'memberId', CollabValidation_1.isCollabMemberId),
|
|
832
|
+
operationId,
|
|
833
|
+
principalSha256: sha256(value, 'principalSha256'),
|
|
834
|
+
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
835
|
+
requestFingerprint: sha256(value, 'requestFingerprint'),
|
|
836
|
+
resultSha256,
|
|
837
|
+
state,
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function decodeContinuityRecord(kind, source, recordId, revision) {
|
|
842
|
+
switch (kind) {
|
|
843
|
+
case 'lifecycle-journal': return lifecycleJournalRecord(source, recordId, revision);
|
|
844
|
+
case 'authority-transfer-recovery':
|
|
845
|
+
return authorityTransferRecoveryRecord(source, recordId, revision);
|
|
846
|
+
case 'transferred-membership-claim':
|
|
847
|
+
return transferredMembershipClaimRecord(source, recordId, revision);
|
|
848
|
+
case 'transfer-receipt-key': return transferReceiptKeyRecord(source, recordId, revision);
|
|
849
|
+
case 'transfer-claim-batch-receipt':
|
|
850
|
+
return transferClaimBatchReceiptRecord(source, recordId, revision);
|
|
851
|
+
case 'transfer-redemption-receipt':
|
|
852
|
+
return transferRedemptionReceiptRecord(source, recordId, revision);
|
|
853
|
+
case 'terminal-principal': return terminalPrincipalRecord(source, recordId, revision);
|
|
854
|
+
case 'terminal-responder-replay':
|
|
855
|
+
return terminalResponderReplayRecord(source, recordId, revision);
|
|
856
|
+
case 'leave-former-principal-replay':
|
|
857
|
+
return leaveFormerPrincipalReplayRecord(source, recordId, revision);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function kindOrder(kind) {
|
|
861
|
+
return exports.COLLAB_PROJECT_BACKUP_RECORD_KINDS.indexOf(kind);
|
|
862
|
+
}
|
|
863
|
+
function compareRecords(left, right) {
|
|
864
|
+
const difference = kindOrder(left.kind) - kindOrder(right.kind);
|
|
865
|
+
return difference === 0
|
|
866
|
+
? left.recordId.localeCompare(right.recordId, 'en-US')
|
|
867
|
+
: difference;
|
|
868
|
+
}
|
|
869
|
+
function recordProjectId(item) {
|
|
870
|
+
if (item.kind === 'cloud-event')
|
|
871
|
+
return item.value.event.projectId;
|
|
872
|
+
if (item.kind === 'protected-claim-envelope')
|
|
873
|
+
return item.value.associatedData.projectId;
|
|
874
|
+
if (item.kind === 'transfer-claim-batch-receipt')
|
|
875
|
+
return item.value.receipt.projectId;
|
|
876
|
+
return item.value.projectId;
|
|
877
|
+
}
|
|
878
|
+
function lifecycleJournalHasInvalidSemantics(item) {
|
|
879
|
+
const value = item.value;
|
|
880
|
+
const effectivePhase = value.state === 'recovery-required'
|
|
881
|
+
? value.recoveryFromPhase
|
|
882
|
+
: value.phase;
|
|
883
|
+
if (effectivePhase === null)
|
|
884
|
+
return true;
|
|
885
|
+
const hasResult = value.resultSha256 !== null;
|
|
886
|
+
const hasCheckpoint = value.checkpointSha256 !== null;
|
|
887
|
+
const hasTransferBatch = value.batchRevision !== null || value.batchSha256 !== null;
|
|
888
|
+
if (value.operationKind === 'authority-transfer') {
|
|
889
|
+
return value.state === 'recovery-required'
|
|
890
|
+
|| hasResult !== (value.state === 'completed');
|
|
891
|
+
}
|
|
892
|
+
if (hasTransferBatch)
|
|
893
|
+
return true;
|
|
894
|
+
if (value.operationKind === 'backup' || value.operationKind === 'export') {
|
|
895
|
+
const statePhaseInvalid = value.state === 'active'
|
|
896
|
+
? !BACKUP_EXPORT_ACTIVE_PHASE_SET.has(value.phase)
|
|
897
|
+
: value.state === 'recovery-required'
|
|
898
|
+
? value.phase !== value.recoveryFromPhase
|
|
899
|
+
|| !BACKUP_EXPORT_ACTIVE_PHASE_SET.has(effectivePhase)
|
|
900
|
+
: value.state === 'cancelled'
|
|
901
|
+
? value.phase !== 'cancelled'
|
|
902
|
+
: value.phase !== 'completed';
|
|
903
|
+
const checkpointRequired = BACKUP_EXPORT_CHECKPOINT_REQUIRED_PHASE_SET.has(effectivePhase);
|
|
904
|
+
const checkpointOptional = effectivePhase === 'cancel-intent'
|
|
905
|
+
|| effectivePhase === 'cancelled';
|
|
906
|
+
const resultRequired = effectivePhase === 'artifact-published'
|
|
907
|
+
|| value.state === 'completed';
|
|
908
|
+
return statePhaseInvalid
|
|
909
|
+
|| (!checkpointOptional && hasCheckpoint !== checkpointRequired)
|
|
910
|
+
|| hasResult !== resultRequired;
|
|
911
|
+
}
|
|
912
|
+
if (hasCheckpoint)
|
|
913
|
+
return true;
|
|
914
|
+
if (value.operationKind === 'leave') {
|
|
915
|
+
const statePhaseValid = (value.state === 'active' && (value.phase === 'prepared'
|
|
916
|
+
|| value.phase === 'membership-left'
|
|
917
|
+
|| value.phase === 'personal-ref-removed')) || (value.state === 'recovery-required'
|
|
918
|
+
&& value.phase === 'recovery-required'
|
|
919
|
+
&& value.recoveryFromPhase === 'membership-left') || (value.state === 'cancelled'
|
|
920
|
+
&& value.phase === 'manager-succession-required') || (value.state === 'completed' && value.phase === 'completed');
|
|
921
|
+
return !statePhaseValid || hasResult !== (value.state === 'completed');
|
|
922
|
+
}
|
|
923
|
+
if (value.operationKind === 'retire') {
|
|
924
|
+
return value.state !== 'completed' || value.phase !== 'completed' || !hasResult;
|
|
925
|
+
}
|
|
926
|
+
const deleteStatePhaseValid = (value.state === 'active'
|
|
927
|
+
&& DELETE_ACTIVE_PHASE_SET.has(value.phase))
|
|
928
|
+
|| (value.state === 'completed' && value.phase === 'completed');
|
|
929
|
+
return !deleteStatePhaseValid || hasResult !== (value.state === 'completed');
|
|
930
|
+
}
|
|
931
|
+
function validateContinuity(records) {
|
|
932
|
+
if (records.length === 0
|
|
933
|
+
|| records[0].kind !== 'project'
|
|
934
|
+
|| records.some((item, index) => index > 0 && compareRecords(records[index - 1], item) >= 0))
|
|
935
|
+
throw invalidPayload('records');
|
|
936
|
+
const projectId = records[0].value.projectId;
|
|
937
|
+
const projectAuthorityGeneration = records[0].value.authorityGeneration;
|
|
938
|
+
if (records.some(item => recordProjectId(item) !== projectId))
|
|
939
|
+
throw invalidPayload('records');
|
|
940
|
+
const memberRecords = new Map(records
|
|
941
|
+
.filter(item => item.kind === 'member')
|
|
942
|
+
.map(item => [item.value.memberId, item]));
|
|
943
|
+
const members = new Set(memberRecords.keys());
|
|
944
|
+
const principalBindingRecords = records.filter(item => item.kind === 'principal-binding');
|
|
945
|
+
const principalBindings = new Map(principalBindingRecords
|
|
946
|
+
.map(item => [item.value.memberId, item.value.principalId]));
|
|
947
|
+
const lifecycles = new Map(records
|
|
948
|
+
.filter((item) => (item.kind === 'lifecycle-journal'))
|
|
949
|
+
.map(item => [item.value.operationId, item]));
|
|
950
|
+
const recoveries = new Map(records
|
|
951
|
+
.filter((item) => (item.kind === 'authority-transfer-recovery'))
|
|
952
|
+
.map(item => [item.value.transferId, item]));
|
|
953
|
+
const keys = new Map(records
|
|
954
|
+
.filter((item) => (item.kind === 'transfer-receipt-key'))
|
|
955
|
+
.map(item => [`${item.value.transferId}:${item.value.receiptKeyId}`, item]));
|
|
956
|
+
const claimRecords = records
|
|
957
|
+
.filter((item) => (item.kind === 'transferred-membership-claim'));
|
|
958
|
+
const claims = new Map(claimRecords
|
|
959
|
+
.map(item => [`${item.value.transferId}:${item.value.memberId}`, item]));
|
|
960
|
+
const redemptionReceiptRecords = records
|
|
961
|
+
.filter((item) => (item.kind === 'transfer-redemption-receipt'));
|
|
962
|
+
const redemptionReceipts = new Map(redemptionReceiptRecords
|
|
963
|
+
.map(item => [`${item.value.receipt.transferId}:${item.value.receipt.memberId}`, item]));
|
|
964
|
+
const protectedEnvelopes = new Map(records
|
|
965
|
+
.filter(item => item.kind === 'protected-claim-envelope')
|
|
966
|
+
.map(item => [`${item.value.transferId}:${item.value.memberId}`, item]));
|
|
967
|
+
const batchReceipts = new Map(records
|
|
968
|
+
.filter((item) => (item.kind === 'transfer-claim-batch-receipt'))
|
|
969
|
+
.map(item => [item.value.receipt.transferId, item]));
|
|
970
|
+
const terminalResponders = new Map(records
|
|
971
|
+
.filter((item) => (item.kind === 'terminal-responder'))
|
|
972
|
+
.map(item => [item.value.operationId, item]));
|
|
973
|
+
const terminalPrincipals = new Map(records
|
|
974
|
+
.filter((item) => (item.kind === 'terminal-principal'))
|
|
975
|
+
.map(item => [`${item.value.operationId}:${item.value.memberId}`, item]));
|
|
976
|
+
const responderReplays = new Map(records
|
|
977
|
+
.filter((item) => (item.kind === 'terminal-responder-replay'))
|
|
978
|
+
.map(item => [item.value.operationId, item]));
|
|
979
|
+
const leaveReplays = new Map(records
|
|
980
|
+
.filter((item) => (item.kind === 'leave-former-principal-replay'))
|
|
981
|
+
.map(item => [item.value.operationId, item]));
|
|
982
|
+
const nonterminalLifecycles = [...lifecycles.values()].filter(item => (item.value.state === 'active' || item.value.state === 'recovery-required'));
|
|
983
|
+
if (nonterminalLifecycles.length > 1
|
|
984
|
+
|| principalBindingRecords.some(item => (memberRecords.get(item.value.memberId)?.value.status !== 'active'))
|
|
985
|
+
|| new Set(principalBindingRecords.map(item => item.value.principalId)).size
|
|
986
|
+
!== principalBindingRecords.length
|
|
987
|
+
|| new Set(claimRecords.map(item => (`${item.value.transferId}:${item.value.claimSha256}`))).size !== claimRecords.length
|
|
988
|
+
|| new Set(redemptionReceiptRecords.map(item => (`${item.value.receipt.transferId}:${item.value.receipt.receiptId}`))).size !== redemptionReceiptRecords.length)
|
|
989
|
+
throw invalidPayload('records');
|
|
990
|
+
for (const lifecycle of lifecycles.values()) {
|
|
991
|
+
if (lifecycleJournalHasInvalidSemantics(lifecycle)
|
|
992
|
+
|| (lifecycle.value.actorMemberId !== null
|
|
993
|
+
&& !members.has(lifecycle.value.actorMemberId))) {
|
|
994
|
+
throw invalidPayload('records');
|
|
995
|
+
}
|
|
996
|
+
if (lifecycle.value.operationKind === 'authority-transfer'
|
|
997
|
+
&& !recoveries.has(lifecycle.value.operationId))
|
|
998
|
+
throw invalidPayload('records');
|
|
999
|
+
if (lifecycle.value.operationKind === 'leave') {
|
|
1000
|
+
const actorMemberId = lifecycle.value.actorMemberId;
|
|
1001
|
+
const actor = actorMemberId === null ? undefined : memberRecords.get(actorMemberId);
|
|
1002
|
+
const replay = leaveReplays.get(lifecycle.value.operationId);
|
|
1003
|
+
const effectivePhase = lifecycle.value.state === 'recovery-required'
|
|
1004
|
+
? lifecycle.value.recoveryFromPhase
|
|
1005
|
+
: lifecycle.value.phase;
|
|
1006
|
+
const afterMembershipSettlement = effectivePhase === 'membership-left'
|
|
1007
|
+
|| effectivePhase === 'personal-ref-removed'
|
|
1008
|
+
|| effectivePhase === 'completed';
|
|
1009
|
+
if (actor === undefined
|
|
1010
|
+
|| (lifecycle.value.state === 'completed') !== (lifecycle.value.phase === 'completed')
|
|
1011
|
+
|| (afterMembershipSettlement && (actor.value.status !== 'left'
|
|
1012
|
+
|| principalBindings.has(actor.value.memberId)
|
|
1013
|
+
|| replay === undefined))
|
|
1014
|
+
|| (!afterMembershipSettlement && (actor.value.status !== 'active'
|
|
1015
|
+
|| !principalBindings.has(actor.value.memberId)
|
|
1016
|
+
|| replay !== undefined)))
|
|
1017
|
+
throw invalidPayload('records');
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
for (const recovery of recoveries.values()) {
|
|
1021
|
+
const lifecycle = lifecycles.get(recovery.value.transferId);
|
|
1022
|
+
if (lifecycle === undefined)
|
|
1023
|
+
throw invalidPayload('records');
|
|
1024
|
+
const proof = recovery.value.relinquishmentProof;
|
|
1025
|
+
const batchReceipt = batchReceipts.get(recovery.value.transferId)?.value.receipt;
|
|
1026
|
+
const evidence = recovery.value.sourceEvidence ?? recovery.value.targetEvidence;
|
|
1027
|
+
const receiptKey = evidence === null
|
|
1028
|
+
? undefined
|
|
1029
|
+
: keys.get(`${recovery.value.transferId}:${evidence.receiptKeyId}`);
|
|
1030
|
+
const effectivePhase = lifecycle.value.state === 'recovery-required'
|
|
1031
|
+
? lifecycle.value.recoveryFromPhase
|
|
1032
|
+
: lifecycle.value.phase;
|
|
1033
|
+
let relinquishmentRequired;
|
|
1034
|
+
try {
|
|
1035
|
+
if (lifecycle.value.direction !== null && effectivePhase !== null) {
|
|
1036
|
+
relinquishmentRequired = (0, CollabAuthorityTransfer_1.decodeCollabAuthorityTransferLifecycleFence)({
|
|
1037
|
+
batchRevision: lifecycle.value.batchRevision,
|
|
1038
|
+
batchSha256: lifecycle.value.batchSha256,
|
|
1039
|
+
checkpointSha256: lifecycle.value.checkpointSha256,
|
|
1040
|
+
direction: lifecycle.value.direction,
|
|
1041
|
+
phase: effectivePhase,
|
|
1042
|
+
}).relinquishmentRequired;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch {
|
|
1046
|
+
throw invalidPayload('records');
|
|
1047
|
+
}
|
|
1048
|
+
const direction = lifecycle.value.direction;
|
|
1049
|
+
const cancellationIndex = effectivePhase === null
|
|
1050
|
+
? undefined
|
|
1051
|
+
: CANCELLATION_PHASE_INDEX.get(effectivePhase);
|
|
1052
|
+
const isCancellation = cancellationIndex !== undefined;
|
|
1053
|
+
const cleanupCompleted = isCancellation && cancellationIndex >= 2;
|
|
1054
|
+
const normalPhaseIndex = direction === 'cloud-to-lan'
|
|
1055
|
+
? CollabAuthorityTransfer_1.COLLAB_CLOUD_TO_LAN_TRANSFER_PHASES.indexOf(effectivePhase)
|
|
1056
|
+
: CollabAuthorityTransfer_1.COLLAB_LAN_TO_CLOUD_TRANSFER_PHASES.indexOf(effectivePhase);
|
|
1057
|
+
const stageRequired = !isCancellation && normalPhaseIndex >= 3;
|
|
1058
|
+
const targetEvidenceRequired = direction === 'cloud-to-lan'
|
|
1059
|
+
&& !isCancellation
|
|
1060
|
+
&& normalPhaseIndex >= 1;
|
|
1061
|
+
const activationEvidenceRequired = direction === 'cloud-to-lan'
|
|
1062
|
+
&& !isCancellation
|
|
1063
|
+
&& normalPhaseIndex >= 6;
|
|
1064
|
+
const publicationRequired = direction === 'lan-to-cloud'
|
|
1065
|
+
&& !isCancellation
|
|
1066
|
+
&& normalPhaseIndex >= 5;
|
|
1067
|
+
const publicationForbidden = direction === 'cloud-to-lan'
|
|
1068
|
+
|| (!isCancellation && normalPhaseIndex < 4);
|
|
1069
|
+
const sourceReopenRequired = isCancellation && (direction === 'cloud-to-lan'
|
|
1070
|
+
? cancellationIndex >= 1
|
|
1071
|
+
: cancellationIndex >= 3);
|
|
1072
|
+
const recoveryEvidenceInvalid = direction === null
|
|
1073
|
+
|| (direction === 'lan-to-cloud' && recovery.value.sourceEvidence === null)
|
|
1074
|
+
|| (targetEvidenceRequired && recovery.value.targetEvidence === null)
|
|
1075
|
+
|| (!isCancellation && (stageRequired !== (recovery.value.stageSha256 !== null)))
|
|
1076
|
+
|| (direction === 'lan-to-cloud'
|
|
1077
|
+
&& recovery.value.stageSha256 !== null
|
|
1078
|
+
&& recovery.value.stageSha256 !== lifecycle.value.checkpointSha256)
|
|
1079
|
+
|| activationEvidenceRequired !== (recovery.value.targetActivationProof !== null
|
|
1080
|
+
&& recovery.value.targetActivationRequestSha256 !== null)
|
|
1081
|
+
|| ((recovery.value.targetActivationProof === null)
|
|
1082
|
+
!== (recovery.value.targetActivationRequestSha256 === null))
|
|
1083
|
+
|| (publicationRequired && recovery.value.inactivePublication === null)
|
|
1084
|
+
|| (publicationForbidden && recovery.value.inactivePublication !== null)
|
|
1085
|
+
|| isCancellation !== (recovery.value.cancellationRequestSha256 !== null)
|
|
1086
|
+
|| sourceReopenRequired !== (recovery.value.sourceReopenSha256 !== null);
|
|
1087
|
+
const expectedProjectAuthorityGeneration = relinquishmentRequired === true
|
|
1088
|
+
? recovery.value.targetAuthority.generation
|
|
1089
|
+
: recovery.value.sourceAuthority.generation;
|
|
1090
|
+
if (recoveryEvidenceInvalid)
|
|
1091
|
+
throw invalidPayload('records');
|
|
1092
|
+
if (cleanupCompleted && ((direction === 'cloud-to-lan' && [...protectedEnvelopes.values()].some(item => (item.value.transferId === recovery.value.transferId)))
|
|
1093
|
+
|| (direction === 'lan-to-cloud' && claimRecords.some(item => (item.value.transferId === recovery.value.transferId)))))
|
|
1094
|
+
throw invalidPayload('records');
|
|
1095
|
+
if (projectAuthorityGeneration !== expectedProjectAuthorityGeneration) {
|
|
1096
|
+
throw invalidPayload('records');
|
|
1097
|
+
}
|
|
1098
|
+
if (!isCancellation) {
|
|
1099
|
+
const hostMemberId = direction === 'lan-to-cloud'
|
|
1100
|
+
? recovery.value.sourceHostMemberId
|
|
1101
|
+
: recovery.value.targetHostMemberId;
|
|
1102
|
+
const eligibleMemberIds = [...memberRecords.values()]
|
|
1103
|
+
.filter(item => item.value.status === 'active' && item.value.memberId !== hostMemberId)
|
|
1104
|
+
.map(item => item.value.memberId);
|
|
1105
|
+
const memberCustodyRequired = normalPhaseIndex >= 3;
|
|
1106
|
+
const batchReceiptRequired = direction === 'cloud-to-lan'
|
|
1107
|
+
? normalPhaseIndex >= 3
|
|
1108
|
+
: normalPhaseIndex >= 4;
|
|
1109
|
+
if ((batchReceipt !== undefined) !== batchReceiptRequired) {
|
|
1110
|
+
throw invalidPayload('records');
|
|
1111
|
+
}
|
|
1112
|
+
for (const memberId of eligibleMemberIds) {
|
|
1113
|
+
const identity = `${recovery.value.transferId}:${memberId}`;
|
|
1114
|
+
const claimPresent = claims.has(identity);
|
|
1115
|
+
const envelopePresent = protectedEnvelopes.has(identity);
|
|
1116
|
+
const redemptionPresent = redemptionReceipts.has(identity);
|
|
1117
|
+
const exactMemberCustody = direction === 'lan-to-cloud'
|
|
1118
|
+
? claimPresent && !envelopePresent
|
|
1119
|
+
: !claimPresent && envelopePresent !== redemptionPresent;
|
|
1120
|
+
if (memberCustodyRequired !== exactMemberCustody) {
|
|
1121
|
+
throw invalidPayload('records');
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (recovery.value.targetEvidence !== null && (recovery.value.targetHostMemberId === null
|
|
1126
|
+
|| principalBindings.get(recovery.value.targetHostMemberId)
|
|
1127
|
+
!== recovery.value.targetEvidence.principalId))
|
|
1128
|
+
throw invalidPayload('records');
|
|
1129
|
+
if (lifecycle.value.operationKind !== 'authority-transfer'
|
|
1130
|
+
|| relinquishmentRequired === undefined
|
|
1131
|
+
|| relinquishmentRequired !== (proof !== null)
|
|
1132
|
+
|| (lifecycle.value.state === 'cancelled') !== (lifecycle.value.phase === 'cancelled')
|
|
1133
|
+
|| (lifecycle.value.state === 'completed') !== (lifecycle.value.phase === 'completed')
|
|
1134
|
+
|| (lifecycle.value.state === 'active'
|
|
1135
|
+
&& (lifecycle.value.phase === 'cancelled' || lifecycle.value.phase === 'completed'))
|
|
1136
|
+
|| lifecycle.value.direction !== (recovery.value.sourceAuthority.kind === 'cloud'
|
|
1137
|
+
? 'cloud-to-lan'
|
|
1138
|
+
: 'lan-to-cloud')
|
|
1139
|
+
|| lifecycle.value.expectedAuthorityGeneration !== recovery.value.sourceAuthority.generation
|
|
1140
|
+
|| lifecycle.value.scheduledAt !== recovery.value.expiresAt
|
|
1141
|
+
|| (recovery.value.sourceHostMemberId !== null
|
|
1142
|
+
&& !members.has(recovery.value.sourceHostMemberId))
|
|
1143
|
+
|| (recovery.value.targetHostMemberId !== null
|
|
1144
|
+
&& !members.has(recovery.value.targetHostMemberId))
|
|
1145
|
+
|| (proof !== null && (proof.batchRevision !== lifecycle.value.batchRevision
|
|
1146
|
+
|| proof.batchSha256 !== lifecycle.value.batchSha256
|
|
1147
|
+
|| proof.checkpointSha256 !== lifecycle.value.checkpointSha256
|
|
1148
|
+
|| (batchReceipt !== undefined && (proof.batchRevision !== batchReceipt.batchRevision
|
|
1149
|
+
|| proof.batchSha256 !== batchReceipt.batchSha256
|
|
1150
|
+
|| proof.checkpointSha256 !== batchReceipt.checkpointSha256))))
|
|
1151
|
+
|| (recovery.value.sourceEvidence !== null
|
|
1152
|
+
&& (recovery.value.sourceEvidence.checkpointManifestSha256
|
|
1153
|
+
!== lifecycle.value.checkpointSha256
|
|
1154
|
+
|| recovery.value.sourceHostMemberId === null
|
|
1155
|
+
|| principalBindings.get(recovery.value.sourceHostMemberId)
|
|
1156
|
+
!== recovery.value.sourceEvidence.principalId))
|
|
1157
|
+
|| (evidence !== null && (receiptKey === undefined
|
|
1158
|
+
|| receiptKey.value.receiptPublicKey !== evidence.receiptPublicKey)))
|
|
1159
|
+
throw invalidPayload('records');
|
|
1160
|
+
}
|
|
1161
|
+
for (const key of keys.values()) {
|
|
1162
|
+
const recovery = recoveries.get(key.value.transferId);
|
|
1163
|
+
const evidence = recovery?.value.sourceEvidence ?? recovery?.value.targetEvidence;
|
|
1164
|
+
if (recovery === undefined
|
|
1165
|
+
|| evidence === null
|
|
1166
|
+
|| evidence === undefined
|
|
1167
|
+
|| evidence.receiptKeyId !== key.value.receiptKeyId
|
|
1168
|
+
|| evidence.receiptPublicKey !== key.value.receiptPublicKey)
|
|
1169
|
+
throw invalidPayload('records');
|
|
1170
|
+
}
|
|
1171
|
+
for (const claim of claims.values()) {
|
|
1172
|
+
const lifecycle = lifecycles.get(claim.value.transferId);
|
|
1173
|
+
const recovery = recoveries.get(claim.value.transferId);
|
|
1174
|
+
const redemptionReceipt = redemptionReceipts.get(`${claim.value.transferId}:${claim.value.memberId}`);
|
|
1175
|
+
if (memberRecords.get(claim.value.memberId)?.value.status !== 'active'
|
|
1176
|
+
|| recovery === undefined
|
|
1177
|
+
|| recovery.value.sourceAuthority.kind !== 'lan'
|
|
1178
|
+
|| claim.value.memberId === recovery.value.sourceHostMemberId
|
|
1179
|
+
|| lifecycle?.value.batchRevision !== claim.value.batchRevision
|
|
1180
|
+
|| lifecycle.value.checkpointSha256 !== claim.value.checkpointSha256
|
|
1181
|
+
|| claim.value.expiresAt !== recovery.value.expiresAt
|
|
1182
|
+
|| (claim.value.targetPrincipalId !== null
|
|
1183
|
+
&& principalBindings.get(claim.value.memberId) !== claim.value.targetPrincipalId)
|
|
1184
|
+
|| (claim.value.state === 'redeemed') !== (redemptionReceipt !== undefined)) {
|
|
1185
|
+
throw invalidPayload('records');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
for (const terminal of terminalResponders.values()) {
|
|
1189
|
+
const operationKind = terminal.value.operation === 'getProjectAuthorityTransfer'
|
|
1190
|
+
? 'authority-transfer'
|
|
1191
|
+
: 'retire';
|
|
1192
|
+
const principals = terminal.value.eligibleMemberIds.map(memberId => (terminalPrincipals.get(`${terminal.value.operationId}:${memberId}`)));
|
|
1193
|
+
if (principals.some(item => item === undefined)
|
|
1194
|
+
|| principals.some(item => item?.value.operationKind !== operationKind)
|
|
1195
|
+
|| new Set(principals.map(item => item?.value.principalId)).size !== principals.length
|
|
1196
|
+
|| (operationKind === 'authority-transfer')
|
|
1197
|
+
!== responderReplays.has(terminal.value.operationId))
|
|
1198
|
+
throw invalidPayload('records');
|
|
1199
|
+
}
|
|
1200
|
+
for (const item of records) {
|
|
1201
|
+
if (item.kind === 'idempotency-result' && !members.has(item.value.memberId)) {
|
|
1202
|
+
throw invalidPayload('records');
|
|
1203
|
+
}
|
|
1204
|
+
if (item.kind === 'protected-claim-envelope') {
|
|
1205
|
+
const identity = `${item.value.transferId}:${item.value.memberId}`;
|
|
1206
|
+
const recovery = recoveries.get(item.value.transferId);
|
|
1207
|
+
const lifecycle = lifecycles.get(item.value.transferId);
|
|
1208
|
+
if (item.recordId !== identity
|
|
1209
|
+
|| recovery === undefined
|
|
1210
|
+
|| lifecycle === undefined
|
|
1211
|
+
|| memberRecords.get(item.value.memberId)?.value.status !== 'active'
|
|
1212
|
+
|| item.value.memberId === recovery.value.targetHostMemberId
|
|
1213
|
+
|| item.value.associatedData.authorityGeneration
|
|
1214
|
+
!== recovery.value.sourceAuthority.generation
|
|
1215
|
+
|| recovery.value.sourceAuthority.kind !== 'cloud'
|
|
1216
|
+
|| item.value.associatedData.checkpointSha256 !== lifecycle.value.checkpointSha256
|
|
1217
|
+
|| item.value.expiresAt !== recovery.value.expiresAt
|
|
1218
|
+
|| !keys.has(`${item.value.transferId}:${item.value.receiptKeyId}`)
|
|
1219
|
+
|| claims.has(identity)
|
|
1220
|
+
|| redemptionReceipts.has(identity))
|
|
1221
|
+
throw invalidPayload('records');
|
|
1222
|
+
}
|
|
1223
|
+
if (item.kind === 'transfer-claim-batch-receipt') {
|
|
1224
|
+
const lifecycle = lifecycles.get(item.value.receipt.transferId);
|
|
1225
|
+
const recovery = recoveries.get(item.value.receipt.transferId);
|
|
1226
|
+
if (lifecycle === undefined
|
|
1227
|
+
|| recovery === undefined
|
|
1228
|
+
|| lifecycle.value.batchRevision !== item.value.receipt.batchRevision
|
|
1229
|
+
|| lifecycle.value.batchSha256 !== item.value.receipt.batchSha256
|
|
1230
|
+
|| lifecycle.value.checkpointSha256 !== item.value.receipt.checkpointSha256
|
|
1231
|
+
|| item.value.receipt.custodyAuthority.kind
|
|
1232
|
+
!== recovery.value.sourceAuthority.kind
|
|
1233
|
+
|| item.value.receipt.custodyAuthority.generation
|
|
1234
|
+
!== recovery.value.sourceAuthority.generation
|
|
1235
|
+
|| item.value.receipt.targetAuthorityGeneration
|
|
1236
|
+
!== recovery.value.targetAuthority.generation
|
|
1237
|
+
|| item.value.receipt.submittedByMemberId !== (recovery.value.sourceAuthority.kind === 'lan'
|
|
1238
|
+
? recovery.value.sourceHostMemberId
|
|
1239
|
+
: recovery.value.targetHostMemberId)
|
|
1240
|
+
|| !members.has(item.value.receipt.submittedByMemberId))
|
|
1241
|
+
throw invalidPayload('records');
|
|
1242
|
+
}
|
|
1243
|
+
if (item.kind === 'transfer-redemption-receipt') {
|
|
1244
|
+
const receipt = item.value.receipt;
|
|
1245
|
+
const identity = `${receipt.transferId}:${receipt.memberId}`;
|
|
1246
|
+
const claim = claims.get(identity);
|
|
1247
|
+
const recovery = recoveries.get(receipt.transferId);
|
|
1248
|
+
const lifecycle = lifecycles.get(receipt.transferId);
|
|
1249
|
+
const terminalPrincipal = terminalPrincipals.get(identity);
|
|
1250
|
+
const lanToCloud = recovery?.value.sourceAuthority.kind === 'lan';
|
|
1251
|
+
if (recovery === undefined
|
|
1252
|
+
|| lifecycle === undefined
|
|
1253
|
+
|| memberRecords.get(receipt.memberId)?.value.status !== 'active'
|
|
1254
|
+
|| receipt.targetAuthorityGeneration !== recovery.value.targetAuthority.generation
|
|
1255
|
+
|| !keys.has(`${receipt.transferId}:${receipt.receiptKeyId}`)
|
|
1256
|
+
|| Date.parse(receipt.redeemedAt) > Date.parse(recovery.value.expiresAt)
|
|
1257
|
+
|| (lanToCloud && (item.value.acknowledgedAt !== null
|
|
1258
|
+
|| claim === undefined
|
|
1259
|
+
|| claim.value.claimSha256 !== receipt.claimSha256
|
|
1260
|
+
|| claim.value.checkpointSha256 !== receipt.checkpointSha256
|
|
1261
|
+
|| claim.value.operationIntentId !== receipt.operationIntentId
|
|
1262
|
+
|| claim.value.redemptionReceiptId !== receipt.receiptId))
|
|
1263
|
+
|| (!lanToCloud && (item.value.acknowledgedAt === null
|
|
1264
|
+
|| receipt.memberId === recovery.value.targetHostMemberId
|
|
1265
|
+
|| receipt.checkpointSha256 !== lifecycle.value.checkpointSha256
|
|
1266
|
+
|| claim !== undefined
|
|
1267
|
+
|| protectedEnvelopes.has(identity)
|
|
1268
|
+
|| terminalPrincipal?.value.acknowledgedAt !== item.value.acknowledgedAt)))
|
|
1269
|
+
throw invalidPayload('records');
|
|
1270
|
+
}
|
|
1271
|
+
if (item.kind === 'terminal-principal') {
|
|
1272
|
+
const terminal = terminalResponders.get(item.value.operationId);
|
|
1273
|
+
const acknowledgement = terminal?.value.acknowledgements.find(value => (value.memberId === item.value.memberId));
|
|
1274
|
+
const expectedOperationKind = terminal?.value.operation === 'getProjectAuthorityTransfer'
|
|
1275
|
+
? 'authority-transfer'
|
|
1276
|
+
: terminal?.value.operation === 'retireProject'
|
|
1277
|
+
? 'retire'
|
|
1278
|
+
: undefined;
|
|
1279
|
+
if (!members.has(item.value.memberId)
|
|
1280
|
+
|| terminal === undefined
|
|
1281
|
+
|| expectedOperationKind !== item.value.operationKind
|
|
1282
|
+
|| !terminal.value.eligibleMemberIds.includes(item.value.memberId)
|
|
1283
|
+
|| (item.value.acknowledgedAt === null) !== (acknowledgement === undefined)
|
|
1284
|
+
|| (acknowledgement !== undefined && (acknowledgement.acknowledgedAt !== item.value.acknowledgedAt
|
|
1285
|
+
|| acknowledgement.principalId !== item.value.principalId))
|
|
1286
|
+
|| (item.value.acknowledgedAt !== null
|
|
1287
|
+
&& Date.parse(item.value.acknowledgedAt) > Date.parse(terminal.value.expiresAt)))
|
|
1288
|
+
throw invalidPayload('records');
|
|
1289
|
+
}
|
|
1290
|
+
if (item.kind === 'terminal-responder-replay') {
|
|
1291
|
+
const lifecycle = lifecycles.get(item.value.operationId);
|
|
1292
|
+
const terminal = terminalResponders.get(item.value.operationId);
|
|
1293
|
+
const recovery = recoveries.get(item.value.operationId);
|
|
1294
|
+
let response;
|
|
1295
|
+
try {
|
|
1296
|
+
response = terminal?.value.operation === 'getProjectAuthorityTransfer'
|
|
1297
|
+
? (0, CollabControlOperationCodecs_1.collabControlOperationCodec)('getProjectAuthorityTransfer').decodeResponse(JSON.parse(terminal.value.responseJson))
|
|
1298
|
+
: undefined;
|
|
1299
|
+
}
|
|
1300
|
+
catch {
|
|
1301
|
+
throw invalidPayload('records');
|
|
1302
|
+
}
|
|
1303
|
+
if (!members.has(item.value.memberId)
|
|
1304
|
+
|| lifecycle?.value.operationKind !== 'authority-transfer'
|
|
1305
|
+
|| lifecycle.value.direction !== 'cloud-to-lan'
|
|
1306
|
+
|| lifecycle.value.state !== 'completed'
|
|
1307
|
+
|| terminal === undefined
|
|
1308
|
+
|| recovery === undefined
|
|
1309
|
+
|| item.value.memberId !== recovery.value.targetHostMemberId
|
|
1310
|
+
|| item.value.requestSha256 !== recovery.value.targetActivationRequestSha256
|
|
1311
|
+
|| !terminal.value.eligibleMemberIds.includes(item.value.memberId)
|
|
1312
|
+
|| response === undefined
|
|
1313
|
+
|| response.batchRevision !== lifecycle.value.batchRevision
|
|
1314
|
+
|| response.batchSha256 !== lifecycle.value.batchSha256
|
|
1315
|
+
|| response.checkpointSha256 !== lifecycle.value.checkpointSha256
|
|
1316
|
+
|| response.direction !== lifecycle.value.direction
|
|
1317
|
+
|| response.expiresAt !== recovery.value.expiresAt
|
|
1318
|
+
|| response.targetUrl !== recovery.value.targetUrl
|
|
1319
|
+
|| JSON.stringify(response.relinquishmentProof)
|
|
1320
|
+
!== JSON.stringify(recovery.value.relinquishmentProof)
|
|
1321
|
+
|| response.sourceAuthority.kind !== recovery.value.sourceAuthority.kind
|
|
1322
|
+
|| response.sourceAuthority.generation !== recovery.value.sourceAuthority.generation
|
|
1323
|
+
|| response.targetAuthority.kind !== recovery.value.targetAuthority.kind
|
|
1324
|
+
|| response.targetAuthority.generation !== recovery.value.targetAuthority.generation)
|
|
1325
|
+
throw invalidPayload('records');
|
|
1326
|
+
}
|
|
1327
|
+
if (item.kind === 'leave-former-principal-replay') {
|
|
1328
|
+
const lifecycle = lifecycles.get(item.value.operationId);
|
|
1329
|
+
if (!members.has(item.value.memberId)
|
|
1330
|
+
|| lifecycle?.value.operationKind !== 'leave'
|
|
1331
|
+
|| lifecycle.value.actorMemberId !== item.value.memberId
|
|
1332
|
+
|| lifecycle.value.expectedPersonalRefOid !== item.value.expectedPersonalRefOid
|
|
1333
|
+
|| lifecycle.value.idempotencyKey !== item.value.intentId
|
|
1334
|
+
|| lifecycle.value.requestFingerprint !== item.value.requestFingerprint
|
|
1335
|
+
|| lifecycle.value.resultSha256 !== item.value.resultSha256
|
|
1336
|
+
|| (item.value.state === 'completed' && (lifecycle.value.state !== 'completed'
|
|
1337
|
+
|| item.value.completedAt !== lifecycle.value.updatedAt))
|
|
1338
|
+
|| (item.value.state === 'recovering' && (lifecycle.value.state !== 'active'
|
|
1339
|
+
&& lifecycle.value.state !== 'recovery-required')))
|
|
1340
|
+
throw invalidPayload('records');
|
|
1341
|
+
}
|
|
1342
|
+
if (item.kind === 'tombstone') {
|
|
1343
|
+
const transferTerminal = [...terminalResponders.values()].find(value => (value.value.operation === 'getProjectAuthorityTransfer'));
|
|
1344
|
+
const lifecycle = transferTerminal === undefined
|
|
1345
|
+
? undefined
|
|
1346
|
+
: lifecycles.get(transferTerminal.value.operationId);
|
|
1347
|
+
const recovery = transferTerminal === undefined
|
|
1348
|
+
? undefined
|
|
1349
|
+
: recoveries.get(transferTerminal.value.operationId);
|
|
1350
|
+
if (transferTerminal !== undefined && (lifecycle === undefined
|
|
1351
|
+
|| recovery === undefined
|
|
1352
|
+
|| item.value.authorityGeneration !== recovery.value.targetAuthority.generation
|
|
1353
|
+
|| item.value.retiredAt !== lifecycle.value.updatedAt))
|
|
1354
|
+
throw invalidPayload('records');
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function manifestWithFormatOne(value) {
|
|
1359
|
+
const source = record(value, 'manifest');
|
|
1360
|
+
if (source.profile !== 'backup'
|
|
1361
|
+
|| source.coordinationFormatVersion !== exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION)
|
|
1362
|
+
throw invalidPayload('manifest');
|
|
1363
|
+
return (0, CollabProjectCheckpoint_1.decodeCollabProjectCheckpointManifest)({
|
|
1364
|
+
...source,
|
|
1365
|
+
coordinationFormatVersion: CollabProjectCheckpoint_1.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
function decodeCollabProjectBackupCheckpointManifest(value) {
|
|
1369
|
+
const decoded = manifestWithFormatOne(value);
|
|
1370
|
+
return {
|
|
1371
|
+
artifacts: decoded.artifacts,
|
|
1372
|
+
coordinationFormatVersion: exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION,
|
|
1373
|
+
createdAt: decoded.createdAt,
|
|
1374
|
+
expectedMainOid: decoded.expectedMainOid,
|
|
1375
|
+
gitObjectFormat: decoded.gitObjectFormat,
|
|
1376
|
+
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
1377
|
+
manifestSha256: decoded.manifestSha256,
|
|
1378
|
+
operationId: decoded.operationId,
|
|
1379
|
+
profile: 'backup',
|
|
1380
|
+
projectId: decoded.projectId,
|
|
1381
|
+
protocolVersion: decoded.protocolVersion,
|
|
1382
|
+
refs: decoded.refs,
|
|
1383
|
+
sourceAuthority: decoded.sourceAuthority,
|
|
1384
|
+
targetAuthority: decoded.targetAuthority,
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function encodeCollabProjectBackupCheckpointManifestCanonicalJson(manifest) {
|
|
1388
|
+
return JSON.stringify(decodeCollabProjectBackupCheckpointManifest(manifest));
|
|
1389
|
+
}
|
|
1390
|
+
function encodeCollabProjectBackupCheckpointManifestDigestInput(manifest) {
|
|
1391
|
+
const decoded = decodeCollabProjectBackupCheckpointManifest(manifest);
|
|
1392
|
+
return JSON.stringify({
|
|
1393
|
+
artifacts: decoded.artifacts,
|
|
1394
|
+
coordinationFormatVersion: decoded.coordinationFormatVersion,
|
|
1395
|
+
createdAt: decoded.createdAt,
|
|
1396
|
+
expectedMainOid: decoded.expectedMainOid,
|
|
1397
|
+
gitObjectFormat: decoded.gitObjectFormat,
|
|
1398
|
+
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
1399
|
+
operationId: decoded.operationId,
|
|
1400
|
+
profile: decoded.profile,
|
|
1401
|
+
projectId: decoded.projectId,
|
|
1402
|
+
protocolVersion: decoded.protocolVersion,
|
|
1403
|
+
refs: decoded.refs,
|
|
1404
|
+
sourceAuthority: decoded.sourceAuthority,
|
|
1405
|
+
targetAuthority: decoded.targetAuthority,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
function decodeCollabProjectBackupCheckpointCoordinationNdjson(value) {
|
|
1409
|
+
if (typeof value !== 'string'
|
|
1410
|
+
|| !value.endsWith('\n')
|
|
1411
|
+
|| !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(value, CollabProjectCheckpoint_1.COLLAB_CHECKPOINT_ARTIFACT_LIMITS.maxCoordinationBytes))
|
|
1412
|
+
throw invalidPayload('coordination');
|
|
1413
|
+
const lines = value.slice(0, -1).split('\n');
|
|
1414
|
+
if (lines.some(line => line.length === 0))
|
|
1415
|
+
throw invalidPayload('coordination');
|
|
1416
|
+
const parsed = lines.map((line) => {
|
|
1417
|
+
let item;
|
|
1418
|
+
try {
|
|
1419
|
+
item = JSON.parse(line);
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
throw invalidPayload('coordination');
|
|
1423
|
+
}
|
|
1424
|
+
if (JSON.stringify(item) !== line)
|
|
1425
|
+
throw invalidPayload('coordination');
|
|
1426
|
+
return { envelope: recordEnvelope(item), item };
|
|
1427
|
+
});
|
|
1428
|
+
const baseItems = parsed.filter(({ envelope }) => (!CONTINUITY_KIND_SET.has(envelope.kind) && envelope.kind !== 'idempotency-result'));
|
|
1429
|
+
const baseDecoded = (0, CollabProjectCheckpoint_1.decodeCollabProjectCheckpointCoordinationNdjson)(baseItems.map(({ item }) => JSON.stringify(sanitizeBackupBasePrincipalRecord(item))).join('\n') + '\n', 'backup');
|
|
1430
|
+
const baseByIdentity = new Map(baseDecoded.map(item => [`${item.kind}\0${item.recordId}`, item]));
|
|
1431
|
+
const decoded = parsed.map(({ envelope }) => {
|
|
1432
|
+
if (envelope.kind === 'idempotency-result') {
|
|
1433
|
+
return idempotencyResultRecord(envelope.source, envelope.recordId, envelope.revision);
|
|
1434
|
+
}
|
|
1435
|
+
if (CONTINUITY_KIND_SET.has(envelope.kind)) {
|
|
1436
|
+
return decodeContinuityRecord(envelope.kind, envelope.source, envelope.recordId, envelope.revision);
|
|
1437
|
+
}
|
|
1438
|
+
const base = baseByIdentity.get(`${envelope.kind}\0${envelope.recordId}`);
|
|
1439
|
+
if (base === undefined)
|
|
1440
|
+
throw invalidPayload('records');
|
|
1441
|
+
return restoreBackupBasePrincipals(envelope.source, base);
|
|
1442
|
+
});
|
|
1443
|
+
if (decoded.some((item, index) => JSON.stringify(item) !== lines[index])) {
|
|
1444
|
+
throw invalidPayload('coordination');
|
|
1445
|
+
}
|
|
1446
|
+
validateContinuity(decoded);
|
|
1447
|
+
return Object.freeze(decoded);
|
|
1448
|
+
}
|
|
1449
|
+
function encodeCollabProjectBackupCheckpointCoordinationNdjson(records) {
|
|
1450
|
+
const value = records.map(item => JSON.stringify(item)).join('\n') + '\n';
|
|
1451
|
+
return decodeCollabProjectBackupCheckpointCoordinationNdjson(value)
|
|
1452
|
+
.map(item => JSON.stringify(item)).join('\n') + '\n';
|
|
1453
|
+
}
|
|
1454
|
+
function validateCollabProjectBackupCheckpointConsistency(manifest, records) {
|
|
1455
|
+
const decodedManifest = decodeCollabProjectBackupCheckpointManifest(manifest);
|
|
1456
|
+
const decodedRecords = decodeCollabProjectBackupCheckpointCoordinationNdjson(records.map(item => JSON.stringify(item)).join('\n') + '\n');
|
|
1457
|
+
if (decodedRecords.some(item => {
|
|
1458
|
+
switch (item.kind) {
|
|
1459
|
+
case 'lifecycle-journal': return item.value.operationId === decodedManifest.operationId;
|
|
1460
|
+
case 'authority-transfer-recovery':
|
|
1461
|
+
case 'transferred-membership-claim':
|
|
1462
|
+
case 'transfer-receipt-key': return item.value.transferId === decodedManifest.operationId;
|
|
1463
|
+
case 'transfer-claim-batch-receipt':
|
|
1464
|
+
return item.value.receipt.transferId === decodedManifest.operationId;
|
|
1465
|
+
case 'transfer-redemption-receipt':
|
|
1466
|
+
return item.value.receipt.transferId === decodedManifest.operationId;
|
|
1467
|
+
case 'terminal-principal':
|
|
1468
|
+
case 'terminal-responder-replay':
|
|
1469
|
+
case 'leave-former-principal-replay':
|
|
1470
|
+
case 'terminal-responder': return item.value.operationId === decodedManifest.operationId;
|
|
1471
|
+
case 'protected-claim-envelope':
|
|
1472
|
+
return item.value.transferId === decodedManifest.operationId;
|
|
1473
|
+
default: return false;
|
|
1474
|
+
}
|
|
1475
|
+
}))
|
|
1476
|
+
throw invalidPayload('records');
|
|
1477
|
+
const baseRecords = decodedRecords.filter(item => (!CONTINUITY_KIND_SET.has(item.kind) && item.kind !== 'idempotency-result'));
|
|
1478
|
+
(0, CollabProjectCheckpoint_1.validateCollabProjectCheckpointConsistency)((0, CollabProjectCheckpoint_1.decodeCollabProjectCheckpointManifest)({
|
|
1479
|
+
...decodedManifest,
|
|
1480
|
+
coordinationFormatVersion: CollabProjectCheckpoint_1.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
1481
|
+
}), baseRecords);
|
|
1482
|
+
return records;
|
|
1483
|
+
}
|