@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.
@@ -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, includeManifestSha256) {
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 !== exports.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION
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: exports.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
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
- function decodeCollabProjectCheckpointManifest(value) {
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, true);
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
- const decoded = decodeCollabProjectCheckpointManifest(manifest);
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 = exactRecord(source.value, '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 terminalResponderRecord(source, recordId, revision) {
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 = exactRecord(item, '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: token(acknowledgement, '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: Object.freeze(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': return principalBindingRecord(source, recordId, revision);
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': return terminalResponderRecord(source, recordId, revision);
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
- function validateRecordSequence(records, profile) {
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 => (principalBindings.get(acknowledgement.memberId) !== acknowledgement.principalId))))
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
- validateRecordSequence(decoded, profile);
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
- validateRecordSequence(records, decodedManifest.profile);
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
- importedClaimState: literal(source, 'importedClaimState', [
350
- 'not-applicable',
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 = 2;
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', `/v2/projects/${projectId}/operations/${operation}`, {
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', `/v2/projects/${projectId}/events?afterSequence=${afterSequence}`, {
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', `/v2/projects/${projectId}/repository.git/info/refs?service=${service}`, { kind: 'git-info-refs', projectId, service });
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', `/v2/projects/${projectId}/repository.git/${routeKind}`, {
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', `/v2/projects/${projectId}/authority-transfers/${transferId}/checkpoint/${artifact}`, {
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', `/v2/projects/${projectId}/checkpoint-exports/${exportId}/checkpoint/${artifact}`, {
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', `/v2/projects/${projectId}/checkpoint-exports/${exportId}`, { exportId, kind: 'project-checkpoint-export', operation, projectId });
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 = '/v2/development/bootstrap/attempts';
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] === 'v2'
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] !== 'v2'
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),
@@ -1,4 +1,4 @@
1
- export const COLLAB_PROTOCOL_VERSION = 6;
1
+ export const COLLAB_PROTOCOL_VERSION = 7;
2
2
  export const COLLAB_MAIN_REF = 'refs/heads/main';
3
3
  export const COLLAB_MEMBER_REF_PREFIX = 'refs/heads/members/';
4
4
  export const COLLAB_LIMITS = Object.freeze({
@@ -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, COLLAB_PROJECT_COORDINATION_FORMAT_VERSION, decodeCollabProjectCheckpointCoordinationNdjson, decodeCollabProjectCheckpointManifest, validateCollabProjectCheckpointConsistency, } from './CollabProjectCheckpoint.mjs';
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 sanitizeBackupBasePrincipalRecord(item) {
202
- const source = record(item, 'record');
203
- const value = record(source.value, 'value');
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 value = exactRecord(source.value, 'value', [
236
- 'acknowledgements',
237
- 'eligibleMemberIds',
238
- 'expiresAt',
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
- return { ...base, value: { ...base.value, acknowledgements } };
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 manifestWithFormatOne(value) {
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 decodeCollabProjectCheckpointManifest({
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
- const decoded = decodeCollabProjectBackupCheckpointManifest(manifest);
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 { envelope: recordEnvelope(item), item };
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 baseDecoded = decodeCollabProjectCheckpointCoordinationNdjson(baseItems.map(({ item }) => JSON.stringify(sanitizeBackupBasePrincipalRecord(item))).join('\n') + '\n', 'backup');
2075
- const baseByIdentity = new Map(baseDecoded.map(item => [`${item.kind}\0${item.recordId}`, item]));
2076
- const decoded = parsed.map(({ envelope }) => {
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
- return restoreBackupBasePrincipals(envelope.source, base);
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
- validateCollabProjectCheckpointConsistency(decodeCollabProjectCheckpointManifest({
2151
- ...decodedManifest,
2152
- coordinationFormatVersion: COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
2153
- }), baseRecords);
2088
+ validateCheckpointRecordSequence(baseRecords, 'backup');
2089
+ validateCheckpointManifestConsistency(decodedManifest, baseRecords);
2154
2090
  return records;
2155
2091
  }