@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.
@@ -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
+ }