@claudian-collab/protocol 3.3.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -28
- package/dist/CollabCloudBinding.d.ts +1 -1
- package/dist/CollabCloudBinding.js +11 -11
- package/dist/CollabCloudProjectSnapshot.d.ts +1 -0
- package/dist/CollabCloudProjectSnapshot.js +2 -0
- package/dist/CollabConstants.d.ts +1 -1
- package/dist/CollabConstants.js +1 -1
- package/dist/CollabProjectBackupCheckpoint.js +42 -106
- package/dist/CollabProjectCheckpoint.d.ts +25 -0
- package/dist/CollabProjectCheckpoint.js +55 -25
- package/dist/CollabProjectMembership.d.ts +3 -5
- package/dist/CollabProjectMembership.js +27 -9
- package/dist/esm/CollabCloudBinding.mjs +11 -11
- package/dist/esm/CollabCloudProjectSnapshot.mjs +2 -0
- package/dist/esm/CollabConstants.mjs +1 -1
- package/dist/esm/CollabProjectBackupCheckpoint.mjs +43 -107
- package/dist/esm/CollabProjectCheckpoint.mjs +50 -25
- package/dist/esm/CollabProjectMembership.mjs +27 -9
- package/package.json +2 -2
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.COLLAB_CHECKPOINT_BACKUP_RECORD_KINDS = exports.COLLAB_CHECKPOINT_PORTABLE_RECORD_KINDS = exports.COLLAB_CHECKPOINT_ARTIFACT_LIMITS = exports.COLLAB_PROJECT_CHECKPOINT_ARTIFACTS = exports.COLLAB_CHECKPOINT_PROFILES = exports.COLLAB_PROTECTED_CLAIM_ENVELOPE_LIMITS = exports.COLLAB_PROTECTED_CLAIM_ENVELOPE_VERSION = exports.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION = exports.COLLAB_PROJECT_CHECKPOINT_MANIFEST_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.decodeCheckpointManifestFields = decodeCheckpointManifestFields;
|
|
4
5
|
exports.decodeCollabProjectCheckpointManifest = decodeCollabProjectCheckpointManifest;
|
|
5
6
|
exports.encodeCollabProjectCheckpointManifestCanonicalJson = encodeCollabProjectCheckpointManifestCanonicalJson;
|
|
6
7
|
exports.encodeCollabProjectCheckpointManifestDigestInput = encodeCollabProjectCheckpointManifestDigestInput;
|
|
8
|
+
exports.encodeCheckpointManifestDigestInput = encodeCheckpointManifestDigestInput;
|
|
7
9
|
exports.encodeCollabProtectedClaimAssociatedData = encodeCollabProtectedClaimAssociatedData;
|
|
10
|
+
exports.decodeCheckpointRecord = decodeCheckpointRecord;
|
|
11
|
+
exports.validateCheckpointRecordSequence = validateCheckpointRecordSequence;
|
|
8
12
|
exports.decodeCollabProjectCheckpointCoordinationNdjson = decodeCollabProjectCheckpointCoordinationNdjson;
|
|
9
13
|
exports.encodeCollabProjectCheckpointCoordinationNdjson = encodeCollabProjectCheckpointCoordinationNdjson;
|
|
10
14
|
exports.validateCollabProjectCheckpointConsistency = validateCollabProjectCheckpointConsistency;
|
|
15
|
+
exports.validateCheckpointManifestConsistency = validateCheckpointManifestConsistency;
|
|
11
16
|
const CollabAuthorityTransfer_1 = require("./CollabAuthorityTransfer");
|
|
12
17
|
const CollabCloudProjectEvent_1 = require("./CollabCloudProjectEvent");
|
|
13
18
|
const CollabConstants_1 = require("./CollabConstants");
|
|
@@ -237,7 +242,7 @@ function gitRefs(value, expectedMainOid) {
|
|
|
237
242
|
throw invalidPayload('refs');
|
|
238
243
|
return Object.freeze(decoded);
|
|
239
244
|
}
|
|
240
|
-
function manifestObject(source,
|
|
245
|
+
function manifestObject(source, coordinationFormatVersion) {
|
|
241
246
|
const profile = literal(source, 'profile', exports.COLLAB_CHECKPOINT_PROFILES);
|
|
242
247
|
const expectedMainOid = token(source, 'expectedMainOid', CollabValidation_1.isCollabGitOid);
|
|
243
248
|
const gitObjectFormat = literal(source, 'gitObjectFormat', ['sha1', 'sha256']);
|
|
@@ -273,15 +278,13 @@ function manifestObject(source, includeManifestSha256) {
|
|
|
273
278
|
sourceAuthority,
|
|
274
279
|
targetAuthority,
|
|
275
280
|
};
|
|
276
|
-
if (common.coordinationFormatVersion !==
|
|
281
|
+
if (common.coordinationFormatVersion !== coordinationFormatVersion
|
|
277
282
|
|| common.manifestSchemaVersion !== exports.COLLAB_PROJECT_CHECKPOINT_MANIFEST_SCHEMA_VERSION
|
|
278
283
|
|| common.protocolVersion !== CollabConstants_1.COLLAB_PROTOCOL_VERSION)
|
|
279
284
|
throw invalidPayload('manifest');
|
|
280
|
-
if (!includeManifestSha256)
|
|
281
|
-
return common;
|
|
282
285
|
return {
|
|
283
286
|
artifacts: common.artifacts,
|
|
284
|
-
coordinationFormatVersion
|
|
287
|
+
coordinationFormatVersion,
|
|
285
288
|
createdAt: common.createdAt,
|
|
286
289
|
expectedMainOid: common.expectedMainOid,
|
|
287
290
|
gitObjectFormat: common.gitObjectFormat,
|
|
@@ -296,7 +299,8 @@ function manifestObject(source, includeManifestSha256) {
|
|
|
296
299
|
targetAuthority: common.targetAuthority,
|
|
297
300
|
};
|
|
298
301
|
}
|
|
299
|
-
|
|
302
|
+
/** @internal Shared field validation; public entry points select their own format. */
|
|
303
|
+
function decodeCheckpointManifestFields(value, coordinationFormatVersion) {
|
|
300
304
|
const source = exactRecord(value, 'manifest', [
|
|
301
305
|
'artifacts',
|
|
302
306
|
'coordinationFormatVersion',
|
|
@@ -313,18 +317,24 @@ function decodeCollabProjectCheckpointManifest(value) {
|
|
|
313
317
|
'sourceAuthority',
|
|
314
318
|
'targetAuthority',
|
|
315
319
|
]);
|
|
316
|
-
const decoded = manifestObject(source,
|
|
320
|
+
const decoded = manifestObject(source, coordinationFormatVersion);
|
|
317
321
|
const encoded = JSON.stringify(decoded);
|
|
318
322
|
if (!(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(encoded, exports.COLLAB_CHECKPOINT_ARTIFACT_LIMITS.maxManifestBytes)) {
|
|
319
323
|
throw invalidPayload('manifest');
|
|
320
324
|
}
|
|
321
325
|
return decoded;
|
|
322
326
|
}
|
|
327
|
+
function decodeCollabProjectCheckpointManifest(value) {
|
|
328
|
+
return decodeCheckpointManifestFields(value, exports.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION);
|
|
329
|
+
}
|
|
323
330
|
function encodeCollabProjectCheckpointManifestCanonicalJson(manifest) {
|
|
324
331
|
return JSON.stringify(decodeCollabProjectCheckpointManifest(manifest));
|
|
325
332
|
}
|
|
326
333
|
function encodeCollabProjectCheckpointManifestDigestInput(manifest) {
|
|
327
|
-
|
|
334
|
+
return encodeCheckpointManifestDigestInput(decodeCollabProjectCheckpointManifest(manifest));
|
|
335
|
+
}
|
|
336
|
+
/** @internal Encode already validated fields without changing their format. */
|
|
337
|
+
function encodeCheckpointManifestDigestInput(decoded) {
|
|
328
338
|
const source = {
|
|
329
339
|
artifacts: decoded.artifacts,
|
|
330
340
|
coordinationFormatVersion: decoded.coordinationFormatVersion,
|
|
@@ -707,13 +717,13 @@ function idempotencyResultRecord(source, recordId, revision) {
|
|
|
707
717
|
},
|
|
708
718
|
};
|
|
709
719
|
}
|
|
710
|
-
function principalBindingRecord(source, recordId, revision) {
|
|
711
|
-
const value =
|
|
720
|
+
function principalBindingRecord(source, recordId, revision, deferPrincipals) {
|
|
721
|
+
const value = principalRecord(source.value, 'value', [
|
|
712
722
|
'boundAt',
|
|
713
723
|
'memberId',
|
|
714
724
|
'principalId',
|
|
715
725
|
'projectId',
|
|
716
|
-
]);
|
|
726
|
+
], deferPrincipals);
|
|
717
727
|
const memberId = token(value, 'memberId', CollabValidation_1.isCollabMemberId);
|
|
718
728
|
if (recordId !== memberId)
|
|
719
729
|
throw invalidPayload('recordId');
|
|
@@ -724,7 +734,7 @@ function principalBindingRecord(source, recordId, revision) {
|
|
|
724
734
|
value: {
|
|
725
735
|
boundAt: timestamp(value, 'boundAt'),
|
|
726
736
|
memberId,
|
|
727
|
-
principalId: token(value, 'principalId'),
|
|
737
|
+
principalId: deferPrincipals ? value.principalId : token(value, 'principalId'),
|
|
728
738
|
projectId: token(value, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
729
739
|
},
|
|
730
740
|
};
|
|
@@ -858,7 +868,13 @@ function lifecycleStateRecord(source, recordId, revision) {
|
|
|
858
868
|
value: internalState,
|
|
859
869
|
};
|
|
860
870
|
}
|
|
861
|
-
function
|
|
871
|
+
function principalRecord(value, field, keys, deferPrincipals) {
|
|
872
|
+
const source = record(value, field);
|
|
873
|
+
return exactRecord(source, field, deferPrincipals && !Object.hasOwn(source, 'principalId')
|
|
874
|
+
? keys.filter(key => key !== 'principalId')
|
|
875
|
+
: keys);
|
|
876
|
+
}
|
|
877
|
+
function terminalResponderRecord(source, recordId, revision, deferPrincipals) {
|
|
862
878
|
const value = exactRecord(source.value, 'value', [
|
|
863
879
|
'acknowledgements',
|
|
864
880
|
'eligibleMemberIds',
|
|
@@ -910,22 +926,24 @@ function terminalResponderRecord(source, recordId, revision) {
|
|
|
910
926
|
if (!Array.isArray(value.acknowledgements))
|
|
911
927
|
throw invalidPayload('acknowledgements');
|
|
912
928
|
const acknowledgements = value.acknowledgements.map((item) => {
|
|
913
|
-
const acknowledgement =
|
|
929
|
+
const acknowledgement = principalRecord(item, 'acknowledgement', [
|
|
914
930
|
'acknowledgedAt',
|
|
915
931
|
'memberId',
|
|
916
932
|
'principalId',
|
|
917
|
-
]);
|
|
933
|
+
], deferPrincipals);
|
|
918
934
|
return {
|
|
919
935
|
acknowledgedAt: timestamp(acknowledgement, 'acknowledgedAt'),
|
|
920
936
|
memberId: token(acknowledgement, 'memberId', CollabValidation_1.isCollabMemberId),
|
|
921
|
-
principalId:
|
|
937
|
+
principalId: deferPrincipals
|
|
938
|
+
? acknowledgement.principalId
|
|
939
|
+
: token(acknowledgement, 'principalId'),
|
|
922
940
|
};
|
|
923
941
|
});
|
|
924
942
|
const acknowledgementPrincipals = new Set();
|
|
925
943
|
acknowledgements.forEach((item, index) => {
|
|
926
944
|
if (!eligibleMemberIds.includes(item.memberId)
|
|
927
945
|
|| (index > 0 && acknowledgements[index - 1].memberId.localeCompare(item.memberId, 'en-US') >= 0)
|
|
928
|
-
|| acknowledgementPrincipals.has(item.principalId))
|
|
946
|
+
|| (!deferPrincipals && acknowledgementPrincipals.has(item.principalId)))
|
|
929
947
|
throw invalidPayload('acknowledgements');
|
|
930
948
|
acknowledgementPrincipals.add(item.principalId);
|
|
931
949
|
});
|
|
@@ -934,7 +952,7 @@ function terminalResponderRecord(source, recordId, revision) {
|
|
|
934
952
|
recordId,
|
|
935
953
|
revision,
|
|
936
954
|
value: {
|
|
937
|
-
acknowledgements
|
|
955
|
+
acknowledgements,
|
|
938
956
|
eligibleMemberIds: Object.freeze(eligibleMemberIds),
|
|
939
957
|
expiresAt,
|
|
940
958
|
operation,
|
|
@@ -1107,8 +1125,9 @@ function authorityVolumePairRecord(source, recordId, revision) {
|
|
|
1107
1125
|
},
|
|
1108
1126
|
};
|
|
1109
1127
|
}
|
|
1110
|
-
function decodeCheckpointRecord(value) {
|
|
1128
|
+
function decodeCheckpointRecord(value, principalPhase) {
|
|
1111
1129
|
const { kind, recordId, revision, source } = recordEnvelope(value);
|
|
1130
|
+
const deferPrincipals = principalPhase === 'deferred';
|
|
1112
1131
|
switch (kind) {
|
|
1113
1132
|
case 'project': return projectRecord(source, recordId, revision);
|
|
1114
1133
|
case 'member': return memberRecord(source, recordId, revision);
|
|
@@ -1121,10 +1140,12 @@ function decodeCheckpointRecord(value) {
|
|
|
1121
1140
|
case 'cloud-event': return cloudEventRecord(source, recordId, revision);
|
|
1122
1141
|
case 'cloud-event-cursor': return cloudEventCursorRecord(source, recordId, revision);
|
|
1123
1142
|
case 'idempotency-result': return idempotencyResultRecord(source, recordId, revision);
|
|
1124
|
-
case 'principal-binding':
|
|
1143
|
+
case 'principal-binding':
|
|
1144
|
+
return principalBindingRecord(source, recordId, revision, deferPrincipals);
|
|
1125
1145
|
case 'repository-placement': return repositoryPlacementRecord(source, recordId, revision);
|
|
1126
1146
|
case 'lifecycle-state': return lifecycleStateRecord(source, recordId, revision);
|
|
1127
|
-
case 'terminal-responder':
|
|
1147
|
+
case 'terminal-responder':
|
|
1148
|
+
return terminalResponderRecord(source, recordId, revision, deferPrincipals);
|
|
1128
1149
|
case 'protected-claim-envelope':
|
|
1129
1150
|
return protectedClaimEnvelopeRecord(source, recordId, revision);
|
|
1130
1151
|
case 'tombstone': return tombstoneRecord(source, recordId, revision);
|
|
@@ -1140,7 +1161,8 @@ function compareRecords(left, right) {
|
|
|
1140
1161
|
const kindOrder = recordKindOrder(left.kind) - recordKindOrder(right.kind);
|
|
1141
1162
|
return kindOrder !== 0 ? kindOrder : left.recordId.localeCompare(right.recordId, 'en-US');
|
|
1142
1163
|
}
|
|
1143
|
-
|
|
1164
|
+
/** @internal V3 decoding checks terminal principals separately; consistency binds current principals. */
|
|
1165
|
+
function validateCheckpointRecordSequence(records, profile, acknowledgementBinding = 'current-principal') {
|
|
1144
1166
|
if (records.length === 0 || records[0].kind !== 'project')
|
|
1145
1167
|
throw invalidPayload('records');
|
|
1146
1168
|
const projectId = records[0].recordId;
|
|
@@ -1243,7 +1265,9 @@ function validateRecordSequence(records, profile) {
|
|
|
1243
1265
|
throw invalidPayload('records');
|
|
1244
1266
|
}
|
|
1245
1267
|
if (item.kind === 'terminal-responder' && (item.value.eligibleMemberIds.some(memberId => !members.has(memberId))
|
|
1246
|
-
|| item.value.acknowledgements.some(acknowledgement => (
|
|
1268
|
+
|| item.value.acknowledgements.some(acknowledgement => (acknowledgementBinding === 'current-principal'
|
|
1269
|
+
? principalBindings.get(acknowledgement.memberId) !== acknowledgement.principalId
|
|
1270
|
+
: !principalBindings.has(acknowledgement.memberId)))))
|
|
1247
1271
|
throw invalidPayload('records');
|
|
1248
1272
|
}
|
|
1249
1273
|
if (profile === 'backup') {
|
|
@@ -1270,11 +1294,13 @@ function decodeCollabProjectCheckpointCoordinationNdjson(value, profile) {
|
|
|
1270
1294
|
throw invalidPayload('coordination');
|
|
1271
1295
|
}
|
|
1272
1296
|
const result = decodeCheckpointRecord(parsed);
|
|
1297
|
+
if (result.kind === 'terminal-responder')
|
|
1298
|
+
Object.freeze(result.value.acknowledgements);
|
|
1273
1299
|
if (JSON.stringify(result) !== line)
|
|
1274
1300
|
throw invalidPayload('coordination');
|
|
1275
1301
|
return result;
|
|
1276
1302
|
});
|
|
1277
|
-
|
|
1303
|
+
validateCheckpointRecordSequence(decoded, profile);
|
|
1278
1304
|
return Object.freeze(decoded);
|
|
1279
1305
|
}
|
|
1280
1306
|
function encodeCollabProjectCheckpointCoordinationNdjson(records, profile) {
|
|
@@ -1306,7 +1332,11 @@ function checkpointRecordGitOids(checkpointRecord) {
|
|
|
1306
1332
|
}
|
|
1307
1333
|
function validateCollabProjectCheckpointConsistency(manifest, records) {
|
|
1308
1334
|
const decodedManifest = decodeCollabProjectCheckpointManifest(manifest);
|
|
1309
|
-
|
|
1335
|
+
validateCheckpointRecordSequence(records, decodedManifest.profile);
|
|
1336
|
+
return validateCheckpointManifestConsistency(decodedManifest, records);
|
|
1337
|
+
}
|
|
1338
|
+
/** @internal Cross-artifact checks operate on validated fields, not another format. */
|
|
1339
|
+
function validateCheckpointManifestConsistency(decodedManifest, records) {
|
|
1310
1340
|
const project = records[0];
|
|
1311
1341
|
if (project.kind !== 'project'
|
|
1312
1342
|
|| project.value.projectId !== decodedManifest.projectId
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CollabTransferredMembershipClaim } from './CollabAuthorityTransfer';
|
|
1
2
|
import type { CollabDecodeResult } from './CollabProtocol';
|
|
2
3
|
import type { CollabGitOid, CollabIdempotencyKey, CollabIsoTimestamp, CollabMemberId, CollabProjectId, CollabRequestId, CollabRole } from './types';
|
|
3
4
|
export declare const COLLAB_PROJECT_MEMBERSHIP_LIMITS: Readonly<{
|
|
@@ -95,6 +96,7 @@ export interface JoinCloudProjectResponse {
|
|
|
95
96
|
export interface CollabProjectMemberSummary {
|
|
96
97
|
readonly bindingState: CollabProjectMemberBindingState;
|
|
97
98
|
readonly displayName: string;
|
|
99
|
+
readonly importedClaimGeneration: number | null;
|
|
98
100
|
readonly importedClaimState: CollabImportedClaimState;
|
|
99
101
|
readonly memberId: CollabMemberId;
|
|
100
102
|
readonly membershipRevision: number;
|
|
@@ -112,13 +114,9 @@ export interface ReissueTransferredMembershipClaimRequest extends CollabProjectM
|
|
|
112
114
|
readonly expectedMembershipRevision: number;
|
|
113
115
|
readonly memberId: CollabMemberId;
|
|
114
116
|
}
|
|
115
|
-
export interface ReissueTransferredMembershipClaimResponse {
|
|
116
|
-
readonly claim: string;
|
|
117
|
+
export interface ReissueTransferredMembershipClaimResponse extends CollabTransferredMembershipClaim {
|
|
117
118
|
readonly claimGeneration: number;
|
|
118
119
|
readonly createdAt: CollabIsoTimestamp;
|
|
119
|
-
readonly expiresAt: CollabIsoTimestamp;
|
|
120
|
-
readonly memberId: CollabMemberId;
|
|
121
|
-
readonly projectId: CollabProjectId;
|
|
122
120
|
readonly secretReplayExpiresAt: CollabIsoTimestamp;
|
|
123
121
|
}
|
|
124
122
|
export type RevokeTransferredMembershipClaimRequest = ReissueTransferredMembershipClaimRequest;
|
|
@@ -338,23 +338,37 @@ function decodeMemberSummary(value) {
|
|
|
338
338
|
const source = exactRecord(value, 'member', [
|
|
339
339
|
'bindingState',
|
|
340
340
|
'displayName',
|
|
341
|
+
'importedClaimGeneration',
|
|
341
342
|
'importedClaimState',
|
|
342
343
|
'memberId',
|
|
343
344
|
'membershipRevision',
|
|
344
345
|
'role',
|
|
345
346
|
]);
|
|
347
|
+
const importedClaimState = literal(source, 'importedClaimState', [
|
|
348
|
+
'not-applicable',
|
|
349
|
+
'original-active',
|
|
350
|
+
'override-active',
|
|
351
|
+
'revoked',
|
|
352
|
+
'expired',
|
|
353
|
+
'redeemed',
|
|
354
|
+
'hidden',
|
|
355
|
+
]);
|
|
356
|
+
const importedClaimGeneration = source.importedClaimGeneration === null
|
|
357
|
+
? null
|
|
358
|
+
: nonNegativeInteger(source, 'importedClaimGeneration');
|
|
359
|
+
if (importedClaimState === 'hidden' || importedClaimState === 'not-applicable') {
|
|
360
|
+
if (importedClaimGeneration !== null)
|
|
361
|
+
throw invalidPayload('importedClaimGeneration');
|
|
362
|
+
}
|
|
363
|
+
else if (importedClaimGeneration === null
|
|
364
|
+
|| (importedClaimState === 'original-active' && importedClaimGeneration !== 0)
|
|
365
|
+
|| (importedClaimState === 'override-active' && importedClaimGeneration === 0))
|
|
366
|
+
throw invalidPayload('importedClaimGeneration');
|
|
346
367
|
return {
|
|
347
368
|
bindingState: literal(source, 'bindingState', ['bound', 'unbound', 'hidden']),
|
|
348
369
|
displayName: boundedText(source, 'displayName', exports.COLLAB_PROJECT_MEMBERSHIP_LIMITS.maxDisplayNameUtf8Bytes),
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
'original-active',
|
|
352
|
-
'override-active',
|
|
353
|
-
'revoked',
|
|
354
|
-
'expired',
|
|
355
|
-
'redeemed',
|
|
356
|
-
'hidden',
|
|
357
|
-
]),
|
|
370
|
+
importedClaimGeneration,
|
|
371
|
+
importedClaimState,
|
|
358
372
|
memberId: token(source, 'memberId', CollabValidation_1.isCollabMemberId),
|
|
359
373
|
membershipRevision: positiveInteger(source, 'membershipRevision'),
|
|
360
374
|
role: literal(source, 'role', ['manager', 'member']),
|
|
@@ -401,6 +415,8 @@ function decodeReissueClaimResponse(value) {
|
|
|
401
415
|
'memberId',
|
|
402
416
|
'projectId',
|
|
403
417
|
'secretReplayExpiresAt',
|
|
418
|
+
'targetAuthorityGeneration',
|
|
419
|
+
'transferId',
|
|
404
420
|
]);
|
|
405
421
|
const createdAt = timestamp(source, 'createdAt');
|
|
406
422
|
const expiresAt = timestamp(source, 'expiresAt');
|
|
@@ -416,6 +432,8 @@ function decodeReissueClaimResponse(value) {
|
|
|
416
432
|
memberId: token(source, 'memberId', CollabValidation_1.isCollabMemberId),
|
|
417
433
|
projectId: token(source, 'projectId', CollabValidation_1.isCollabProjectId),
|
|
418
434
|
secretReplayExpiresAt,
|
|
435
|
+
targetAuthorityGeneration: positiveInteger(source, 'targetAuthorityGeneration'),
|
|
436
|
+
transferId: token(source, 'transferId'),
|
|
419
437
|
};
|
|
420
438
|
}
|
|
421
439
|
function decodeRevokeClaimResponse(value) {
|
|
@@ -3,7 +3,7 @@ import { COLLAB_CONTROL_OPERATION_CODECS, } from './CollabControlOperationCodecs
|
|
|
3
3
|
import { COLLAB_ERROR_CODES, CollabError, sanitizeCollabDiagnosticContext, } from './CollabError.mjs';
|
|
4
4
|
import { isCollabOpaqueId, isCollabProjectId, } from './CollabValidation.mjs';
|
|
5
5
|
import { COLLAB_CHECKPOINT_ARTIFACT_LIMITS, COLLAB_PROJECT_CHECKPOINT_ARTIFACTS, } from './CollabProjectCheckpoint.mjs';
|
|
6
|
-
export const COLLAB_CLOUD_BINDING_VERSION =
|
|
6
|
+
export const COLLAB_CLOUD_BINDING_VERSION = 3;
|
|
7
7
|
export const COLLAB_CLOUD_CAPABILITY_DOCUMENT_SCHEMA_VERSION = 2;
|
|
8
8
|
export const COLLAB_CLOUD_CAPABILITIES = Object.freeze([
|
|
9
9
|
'accept',
|
|
@@ -168,7 +168,7 @@ export function collabCloudProjectOperationRoute(projectId, operation) {
|
|
|
168
168
|
assertProjectId(projectId);
|
|
169
169
|
if (!isCloudJsonOperation(operation))
|
|
170
170
|
invalidRoute();
|
|
171
|
-
return route('POST', `/
|
|
171
|
+
return route('POST', `/v3/projects/${projectId}/operations/${operation}`, {
|
|
172
172
|
kind: 'project-operation',
|
|
173
173
|
operation,
|
|
174
174
|
projectId,
|
|
@@ -178,7 +178,7 @@ export function collabCloudProjectEventsRoute(projectId, afterSequence) {
|
|
|
178
178
|
assertProjectId(projectId);
|
|
179
179
|
if (!Number.isSafeInteger(afterSequence) || afterSequence < 0)
|
|
180
180
|
invalidRoute();
|
|
181
|
-
return route('GET', `/
|
|
181
|
+
return route('GET', `/v3/projects/${projectId}/events?afterSequence=${afterSequence}`, {
|
|
182
182
|
afterSequence,
|
|
183
183
|
kind: 'project-events',
|
|
184
184
|
projectId,
|
|
@@ -189,11 +189,11 @@ export function collabCloudGitRoute(projectId, routeKind, service) {
|
|
|
189
189
|
if (routeKind === 'info-refs') {
|
|
190
190
|
if (service !== 'git-upload-pack' && service !== 'git-receive-pack')
|
|
191
191
|
invalidRoute();
|
|
192
|
-
return route('GET', `/
|
|
192
|
+
return route('GET', `/v3/projects/${projectId}/repository.git/info/refs?service=${service}`, { kind: 'git-info-refs', projectId, service });
|
|
193
193
|
}
|
|
194
194
|
if (service !== undefined)
|
|
195
195
|
invalidRoute();
|
|
196
|
-
return route('POST', `/
|
|
196
|
+
return route('POST', `/v3/projects/${projectId}/repository.git/${routeKind}`, {
|
|
197
197
|
kind: routeKind,
|
|
198
198
|
projectId,
|
|
199
199
|
});
|
|
@@ -204,7 +204,7 @@ export function collabCloudAuthorityTransferArtifactRoute(projectId, transferId,
|
|
|
204
204
|
if (direction !== 'download' && direction !== 'upload'
|
|
205
205
|
|| !COLLAB_PROJECT_CHECKPOINT_ARTIFACTS_SET.has(artifact))
|
|
206
206
|
invalidRoute();
|
|
207
|
-
return route(direction === 'upload' ? 'PUT' : 'GET', `/
|
|
207
|
+
return route(direction === 'upload' ? 'PUT' : 'GET', `/v3/projects/${projectId}/authority-transfers/${transferId}/checkpoint/${artifact}`, {
|
|
208
208
|
artifact,
|
|
209
209
|
direction,
|
|
210
210
|
kind: 'authority-transfer-artifact',
|
|
@@ -217,7 +217,7 @@ export function collabCloudProjectCheckpointExportArtifactRoute(projectId, expor
|
|
|
217
217
|
assertAttemptId(exportId);
|
|
218
218
|
if (!COLLAB_PROJECT_CHECKPOINT_ARTIFACTS_SET.has(artifact))
|
|
219
219
|
invalidRoute();
|
|
220
|
-
return route('GET', `/
|
|
220
|
+
return route('GET', `/v3/projects/${projectId}/checkpoint-exports/${exportId}/checkpoint/${artifact}`, {
|
|
221
221
|
artifact,
|
|
222
222
|
exportId,
|
|
223
223
|
kind: 'project-checkpoint-export-artifact',
|
|
@@ -229,7 +229,7 @@ export function collabCloudProjectCheckpointExportRoute(projectId, exportId, ope
|
|
|
229
229
|
assertAttemptId(exportId);
|
|
230
230
|
if (operation !== 'begin' && operation !== 'status')
|
|
231
231
|
invalidRoute();
|
|
232
|
-
return route(operation === 'begin' ? 'POST' : 'GET', `/
|
|
232
|
+
return route(operation === 'begin' ? 'POST' : 'GET', `/v3/projects/${projectId}/checkpoint-exports/${exportId}`, { exportId, kind: 'project-checkpoint-export', operation, projectId });
|
|
233
233
|
}
|
|
234
234
|
export function decodeCollabCloudProjectCheckpointExportStatus(value) {
|
|
235
235
|
const source = exactRecord(value, 'checkpointExport', [
|
|
@@ -267,7 +267,7 @@ export function decodeCollabCloudProjectCheckpointExportStatus(value) {
|
|
|
267
267
|
});
|
|
268
268
|
}
|
|
269
269
|
export function collabDevelopmentBootstrapRoute(operation, attemptId) {
|
|
270
|
-
const base = '/
|
|
270
|
+
const base = '/v3/development/bootstrap/attempts';
|
|
271
271
|
if (operation === 'beginDevelopmentBootstrap') {
|
|
272
272
|
if (attemptId !== undefined)
|
|
273
273
|
invalidRoute();
|
|
@@ -326,7 +326,7 @@ export function matchCollabCloudRoute(method, target) {
|
|
|
326
326
|
&& url.pathname === '/collab/capabilities'
|
|
327
327
|
&& exactQuery(url, []))
|
|
328
328
|
return { kind: 'capabilities' };
|
|
329
|
-
if (segments[0] === '
|
|
329
|
+
if (segments[0] === 'v3'
|
|
330
330
|
&& segments[1] === 'projects'
|
|
331
331
|
&& isCollabProjectId(segments[2])) {
|
|
332
332
|
const projectId = segments[2];
|
|
@@ -405,7 +405,7 @@ export function matchCollabCloudRoute(method, target) {
|
|
|
405
405
|
&& exactQuery(url, []))
|
|
406
406
|
return { kind: segments[4], projectId };
|
|
407
407
|
}
|
|
408
|
-
if (segments[0] !== '
|
|
408
|
+
if (segments[0] !== 'v3'
|
|
409
409
|
|| segments[1] !== 'development'
|
|
410
410
|
|| segments[2] !== 'bootstrap'
|
|
411
411
|
|| segments[3] !== 'attempts'
|
|
@@ -63,6 +63,7 @@ function positiveInteger(source, field) {
|
|
|
63
63
|
}
|
|
64
64
|
function decodeProject(value) {
|
|
65
65
|
const source = exactRecord(value, 'project', [
|
|
66
|
+
'authorityGeneration',
|
|
66
67
|
'createdAt',
|
|
67
68
|
'expectedMainOid',
|
|
68
69
|
'id',
|
|
@@ -72,6 +73,7 @@ function decodeProject(value) {
|
|
|
72
73
|
if (source.mainRef !== COLLAB_MAIN_REF)
|
|
73
74
|
throw invalidPayload('mainRef');
|
|
74
75
|
return {
|
|
76
|
+
authorityGeneration: positiveInteger(source, 'authorityGeneration'),
|
|
75
77
|
createdAt: timestamp(source, 'createdAt'),
|
|
76
78
|
expectedMainOid: stringField(source, 'expectedMainOid', 64, isCollabGitOid),
|
|
77
79
|
id: stringField(source, 'id', 64, isCollabProjectId),
|
|
@@ -2,7 +2,7 @@ import { COLLAB_AUTHORITY_TRANSFER_CANCELLATION_PHASES, COLLAB_CLOUD_TO_LAN_TRAN
|
|
|
2
2
|
import { COLLAB_MAIN_REF, COLLAB_MEMBER_REF_PREFIX, } from './CollabConstants.mjs';
|
|
3
3
|
import { collabControlOperationCodec, } from './CollabControlOperationCodecs.mjs';
|
|
4
4
|
import { CollabError } from './CollabError.mjs';
|
|
5
|
-
import { COLLAB_CHECKPOINT_ARTIFACT_LIMITS, COLLAB_CHECKPOINT_PORTABLE_RECORD_KINDS,
|
|
5
|
+
import { COLLAB_CHECKPOINT_ARTIFACT_LIMITS, COLLAB_CHECKPOINT_PORTABLE_RECORD_KINDS, decodeCheckpointManifestFields, decodeCheckpointRecord, encodeCheckpointManifestDigestInput, validateCheckpointManifestConsistency, validateCheckpointRecordSequence, } from './CollabProjectCheckpoint.mjs';
|
|
6
6
|
import { COLLAB_PROJECT_MEMBERSHIP_LIMITS, } from './CollabProjectMembership.mjs';
|
|
7
7
|
import { hasUtf8ByteLengthAtMost, isCollabGitOid, isCollabMemberId, isCollabOpaqueId, isCollabProjectId, } from './CollabValidation.mjs';
|
|
8
8
|
export const COLLAB_PROJECT_BACKUP_COMPATIBILITY_STAGE = 'pre-production-replaceable';
|
|
@@ -198,65 +198,19 @@ function absoluteTargetUrl(source) {
|
|
|
198
198
|
function principal(source, field) {
|
|
199
199
|
return token(source, field, value => (typeof value === 'string' && PRINCIPAL_PATTERN.test(value)));
|
|
200
200
|
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (source.kind === 'principal-binding') {
|
|
205
|
-
return {
|
|
206
|
-
...source,
|
|
207
|
-
value: { ...value, principalId: `principal_${String(value.memberId)}` },
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
if (source.kind === 'terminal-responder' && Array.isArray(value.acknowledgements)) {
|
|
211
|
-
return {
|
|
212
|
-
...source,
|
|
213
|
-
value: {
|
|
214
|
-
...value,
|
|
215
|
-
acknowledgements: value.acknowledgements.map(item => ({
|
|
216
|
-
...record(item, 'acknowledgement'),
|
|
217
|
-
principalId: `principal_${String(record(item, 'acknowledgement').memberId)}`,
|
|
218
|
-
})),
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
return item;
|
|
223
|
-
}
|
|
224
|
-
function restoreBackupBasePrincipals(source, base) {
|
|
225
|
-
if (base.kind === 'principal-binding') {
|
|
226
|
-
const value = exactRecord(source.value, 'value', [
|
|
227
|
-
'boundAt', 'memberId', 'principalId', 'projectId',
|
|
228
|
-
]);
|
|
229
|
-
return {
|
|
230
|
-
...base,
|
|
231
|
-
value: { ...base.value, principalId: principal(value, 'principalId') },
|
|
232
|
-
};
|
|
233
|
-
}
|
|
201
|
+
function validateBackupBasePrincipals(base) {
|
|
202
|
+
if (base.kind === 'principal-binding')
|
|
203
|
+
principal(base.value, 'principalId');
|
|
234
204
|
if (base.kind === 'terminal-responder') {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'
|
|
239
|
-
'operation',
|
|
240
|
-
'operationId',
|
|
241
|
-
'projectId',
|
|
242
|
-
'responseJson',
|
|
243
|
-
]);
|
|
244
|
-
const sourceAcknowledgements = value.acknowledgements;
|
|
245
|
-
if (!Array.isArray(sourceAcknowledgements)
|
|
246
|
-
|| sourceAcknowledgements.length !== base.value.acknowledgements.length)
|
|
247
|
-
throw invalidPayload('acknowledgements');
|
|
248
|
-
const acknowledgements = base.value.acknowledgements.map((item, index) => {
|
|
249
|
-
const original = exactRecord(sourceAcknowledgements[index], 'acknowledgement', [
|
|
250
|
-
'acknowledgedAt', 'memberId', 'principalId',
|
|
251
|
-
]);
|
|
252
|
-
return { ...item, principalId: principal(original, 'principalId') };
|
|
253
|
-
});
|
|
254
|
-
if (new Set(acknowledgements.map(item => item.principalId)).size !== acknowledgements.length) {
|
|
255
|
-
throw invalidPayload('acknowledgements');
|
|
205
|
+
for (const acknowledgement of base.value.acknowledgements) {
|
|
206
|
+
if (acknowledgement.principalId === undefined)
|
|
207
|
+
throw invalidPayload('acknowledgement');
|
|
208
|
+
principal(acknowledgement, 'principalId');
|
|
256
209
|
}
|
|
257
|
-
|
|
210
|
+
if (new Set(base.value.acknowledgements.map(item => item.principalId)).size
|
|
211
|
+
!== base.value.acknowledgements.length)
|
|
212
|
+
throw invalidPayload('acknowledgements');
|
|
258
213
|
}
|
|
259
|
-
return base;
|
|
260
214
|
}
|
|
261
215
|
function repositoryPublicationRef(value) {
|
|
262
216
|
const source = exactRecord(value, 'ref', ['name', 'oid']);
|
|
@@ -2000,55 +1954,18 @@ function validateContinuity(records) {
|
|
|
2000
1954
|
}
|
|
2001
1955
|
}
|
|
2002
1956
|
}
|
|
2003
|
-
function
|
|
1957
|
+
export function decodeCollabProjectBackupCheckpointManifest(value) {
|
|
2004
1958
|
const source = record(value, 'manifest');
|
|
2005
1959
|
if (source.profile !== 'backup'
|
|
2006
1960
|
|| source.coordinationFormatVersion !== COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION)
|
|
2007
1961
|
throw invalidPayload('manifest');
|
|
2008
|
-
return
|
|
2009
|
-
...source,
|
|
2010
|
-
coordinationFormatVersion: COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
export function decodeCollabProjectBackupCheckpointManifest(value) {
|
|
2014
|
-
const decoded = manifestWithFormatOne(value);
|
|
2015
|
-
return {
|
|
2016
|
-
artifacts: decoded.artifacts,
|
|
2017
|
-
coordinationFormatVersion: COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION,
|
|
2018
|
-
createdAt: decoded.createdAt,
|
|
2019
|
-
expectedMainOid: decoded.expectedMainOid,
|
|
2020
|
-
gitObjectFormat: decoded.gitObjectFormat,
|
|
2021
|
-
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
2022
|
-
manifestSha256: decoded.manifestSha256,
|
|
2023
|
-
operationId: decoded.operationId,
|
|
2024
|
-
profile: 'backup',
|
|
2025
|
-
projectId: decoded.projectId,
|
|
2026
|
-
protocolVersion: decoded.protocolVersion,
|
|
2027
|
-
refs: decoded.refs,
|
|
2028
|
-
sourceAuthority: decoded.sourceAuthority,
|
|
2029
|
-
targetAuthority: decoded.targetAuthority,
|
|
2030
|
-
};
|
|
1962
|
+
return decodeCheckpointManifestFields(source, COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION);
|
|
2031
1963
|
}
|
|
2032
1964
|
export function encodeCollabProjectBackupCheckpointManifestCanonicalJson(manifest) {
|
|
2033
1965
|
return JSON.stringify(decodeCollabProjectBackupCheckpointManifest(manifest));
|
|
2034
1966
|
}
|
|
2035
1967
|
export function encodeCollabProjectBackupCheckpointManifestDigestInput(manifest) {
|
|
2036
|
-
|
|
2037
|
-
return JSON.stringify({
|
|
2038
|
-
artifacts: decoded.artifacts,
|
|
2039
|
-
coordinationFormatVersion: decoded.coordinationFormatVersion,
|
|
2040
|
-
createdAt: decoded.createdAt,
|
|
2041
|
-
expectedMainOid: decoded.expectedMainOid,
|
|
2042
|
-
gitObjectFormat: decoded.gitObjectFormat,
|
|
2043
|
-
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
2044
|
-
operationId: decoded.operationId,
|
|
2045
|
-
profile: decoded.profile,
|
|
2046
|
-
projectId: decoded.projectId,
|
|
2047
|
-
protocolVersion: decoded.protocolVersion,
|
|
2048
|
-
refs: decoded.refs,
|
|
2049
|
-
sourceAuthority: decoded.sourceAuthority,
|
|
2050
|
-
targetAuthority: decoded.targetAuthority,
|
|
2051
|
-
});
|
|
1968
|
+
return encodeCheckpointManifestDigestInput(decodeCollabProjectBackupCheckpointManifest(manifest));
|
|
2052
1969
|
}
|
|
2053
1970
|
export function decodeCollabProjectBackupCheckpointCoordinationNdjson(value) {
|
|
2054
1971
|
if (typeof value !== 'string'
|
|
@@ -2068,22 +1985,43 @@ export function decodeCollabProjectBackupCheckpointCoordinationNdjson(value) {
|
|
|
2068
1985
|
}
|
|
2069
1986
|
if (JSON.stringify(item) !== line)
|
|
2070
1987
|
throw invalidPayload('coordination');
|
|
2071
|
-
return {
|
|
1988
|
+
return {
|
|
1989
|
+
envelope: recordEnvelope(item),
|
|
1990
|
+
item,
|
|
1991
|
+
base: undefined,
|
|
1992
|
+
};
|
|
2072
1993
|
});
|
|
2073
1994
|
const baseItems = parsed.filter(({ envelope }) => (!CONTINUITY_KIND_SET.has(envelope.kind) && envelope.kind !== 'idempotency-result'));
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
1995
|
+
for (const { envelope } of baseItems) {
|
|
1996
|
+
const value = record(envelope.source.value, 'value');
|
|
1997
|
+
if (envelope.kind === 'terminal-responder' && Array.isArray(value.acknowledgements)) {
|
|
1998
|
+
for (const acknowledgement of value.acknowledgements)
|
|
1999
|
+
record(acknowledgement, 'acknowledgement');
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const baseRecords = [];
|
|
2003
|
+
for (const entry of baseItems) {
|
|
2004
|
+
entry.base = decodeCheckpointRecord(entry.item, 'deferred');
|
|
2005
|
+
if (JSON.stringify(entry.base) !== JSON.stringify(entry.item)
|
|
2006
|
+
|| (entry.base.kind === 'principal-binding' && entry.base.value.principalId === undefined)) {
|
|
2007
|
+
throw invalidPayload('coordination');
|
|
2008
|
+
}
|
|
2009
|
+
baseRecords.push(entry.base);
|
|
2010
|
+
}
|
|
2011
|
+
if (baseRecords.length === 0)
|
|
2012
|
+
throw invalidPayload('coordination');
|
|
2013
|
+
validateCheckpointRecordSequence(baseRecords, 'backup', 'member');
|
|
2014
|
+
const decoded = parsed.map(({ envelope, base }) => {
|
|
2077
2015
|
if (envelope.kind === 'idempotency-result') {
|
|
2078
2016
|
return idempotencyResultRecord(envelope.source, envelope.recordId, envelope.revision);
|
|
2079
2017
|
}
|
|
2080
2018
|
if (CONTINUITY_KIND_SET.has(envelope.kind)) {
|
|
2081
2019
|
return decodeContinuityRecord(envelope.kind, envelope.source, envelope.recordId, envelope.revision);
|
|
2082
2020
|
}
|
|
2083
|
-
const base = baseByIdentity.get(`${envelope.kind}\0${envelope.recordId}`);
|
|
2084
2021
|
if (base === undefined)
|
|
2085
2022
|
throw invalidPayload('records');
|
|
2086
|
-
|
|
2023
|
+
validateBackupBasePrincipals(base);
|
|
2024
|
+
return base;
|
|
2087
2025
|
});
|
|
2088
2026
|
if (decoded.some((item, index) => JSON.stringify(item) !== lines[index])) {
|
|
2089
2027
|
throw invalidPayload('coordination');
|
|
@@ -2147,9 +2085,7 @@ export function validateCollabProjectBackupCheckpointConsistency(manifest, recor
|
|
|
2147
2085
|
}))
|
|
2148
2086
|
throw invalidPayload('records');
|
|
2149
2087
|
const baseRecords = decodedRecords.filter(item => (!CONTINUITY_KIND_SET.has(item.kind) && item.kind !== 'idempotency-result'));
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
coordinationFormatVersion: COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
2153
|
-
}), baseRecords);
|
|
2088
|
+
validateCheckpointRecordSequence(baseRecords, 'backup');
|
|
2089
|
+
validateCheckpointManifestConsistency(decodedManifest, baseRecords);
|
|
2154
2090
|
return records;
|
|
2155
2091
|
}
|