@claudian-collab/protocol 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/esm/CollabAuthorityTransfer.mjs +884 -0
- package/dist/esm/CollabCloudBinding.mjs +552 -0
- package/dist/esm/CollabCloudProjectEvent.mjs +150 -0
- package/dist/esm/CollabCloudProjectSnapshot.mjs +277 -0
- package/dist/esm/CollabConstants.mjs +31 -0
- package/dist/esm/CollabControlOperationCodecs.mjs +104 -0
- package/dist/esm/CollabError.mjs +175 -0
- package/dist/esm/CollabMarkdownProse.mjs +54 -0
- package/dist/esm/CollabMemberMentionParser.mjs +48 -0
- package/dist/esm/CollabProjectBackupCheckpoint.mjs +1473 -0
- package/dist/esm/CollabProjectCheckpoint.mjs +1326 -0
- package/dist/esm/CollabProjectRetirement.mjs +119 -0
- package/dist/esm/CollabProtocol.mjs +64 -0
- package/dist/esm/CollabRequestTicketRequestCodecs.mjs +263 -0
- package/dist/esm/CollabRequestTicketResponseCodecs.mjs +353 -0
- package/dist/esm/CollabTicketReferenceParser.mjs +52 -0
- package/dist/esm/CollabValidation.mjs +19 -0
- package/dist/esm/DevelopmentBootstrap.mjs +537 -0
- package/dist/esm/index.mjs +16 -0
- package/dist/esm/types.mjs +8 -0
- package/package.json +5 -2
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { COLLAB_LIMITS, COLLAB_MAIN_REF, COLLAB_PROTOCOL_VERSION, } from './CollabConstants.mjs';
|
|
2
|
+
import { COLLAB_CLOUD_BINDING_LIMITS, } from './CollabCloudBinding.mjs';
|
|
3
|
+
import { CollabError } from './CollabError.mjs';
|
|
4
|
+
import { collabMemberRef } from './types.mjs';
|
|
5
|
+
import { hasUtf8ByteLengthAtMost, isCollabGitOid, isCollabMemberId, isCollabOpaqueId, isCollabProjectId, } from './CollabValidation.mjs';
|
|
6
|
+
export const DEVELOPMENT_BOOTSTRAP_MANIFEST_SCHEMA_VERSION = 1;
|
|
7
|
+
export const DEVELOPMENT_BOOTSTRAP_ATTEMPT_STATES = Object.freeze([
|
|
8
|
+
'collecting',
|
|
9
|
+
'validating',
|
|
10
|
+
'ready',
|
|
11
|
+
'activating',
|
|
12
|
+
'rejected',
|
|
13
|
+
'cancelled',
|
|
14
|
+
'recovery-required',
|
|
15
|
+
'activated',
|
|
16
|
+
]);
|
|
17
|
+
export const DEVELOPMENT_BOOTSTRAP_ACTIVATION_PHASES = Object.freeze([
|
|
18
|
+
'publish-intent',
|
|
19
|
+
'repository-published',
|
|
20
|
+
'activated',
|
|
21
|
+
'completed',
|
|
22
|
+
]);
|
|
23
|
+
export const DEVELOPMENT_BOOTSTRAP_CANCELLATION_PHASES = Object.freeze([
|
|
24
|
+
'cancel-intent',
|
|
25
|
+
'cancelled',
|
|
26
|
+
'recovery-required',
|
|
27
|
+
]);
|
|
28
|
+
export const DEVELOPMENT_BOOTSTRAP_OPERATIONS = Object.freeze([
|
|
29
|
+
'beginDevelopmentBootstrap',
|
|
30
|
+
'submitDevelopmentBootstrapReport',
|
|
31
|
+
'getDevelopmentBootstrap',
|
|
32
|
+
'activateDevelopmentBootstrap',
|
|
33
|
+
'cancelDevelopmentBootstrap',
|
|
34
|
+
'putDevelopmentBootstrapGitBundle',
|
|
35
|
+
]);
|
|
36
|
+
const ATTEMPT_STATE_SET = new Set(DEVELOPMENT_BOOTSTRAP_ATTEMPT_STATES);
|
|
37
|
+
const ACTIVATION_PHASE_SET = new Set(DEVELOPMENT_BOOTSTRAP_ACTIVATION_PHASES);
|
|
38
|
+
const CANCELLATION_PHASE_SET = new Set(DEVELOPMENT_BOOTSTRAP_CANCELLATION_PHASES);
|
|
39
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
40
|
+
function invalidPayload(field) {
|
|
41
|
+
return new CollabError({
|
|
42
|
+
code: 'protocol-payload-invalid',
|
|
43
|
+
safeContext: { field },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function record(value, field) {
|
|
47
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
48
|
+
throw invalidPayload(field);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function exactRecord(value, field, keys) {
|
|
53
|
+
const source = record(value, field);
|
|
54
|
+
const expected = new Set(keys);
|
|
55
|
+
if (!keys.every(key => Object.hasOwn(source, key))
|
|
56
|
+
|| Object.keys(source).some(key => !expected.has(key)))
|
|
57
|
+
throw invalidPayload(field);
|
|
58
|
+
return source;
|
|
59
|
+
}
|
|
60
|
+
function exactRecordWithOptional(value, field, required, optional) {
|
|
61
|
+
const source = record(value, field);
|
|
62
|
+
const allowed = new Set([...required, ...optional]);
|
|
63
|
+
if (!required.every(key => Object.hasOwn(source, key))
|
|
64
|
+
|| Object.keys(source).some(key => !allowed.has(key)))
|
|
65
|
+
throw invalidPayload(field);
|
|
66
|
+
return source;
|
|
67
|
+
}
|
|
68
|
+
function stringField(source, field, maximum, validate) {
|
|
69
|
+
const value = source[field];
|
|
70
|
+
if (typeof value !== 'string'
|
|
71
|
+
|| value.length === 0
|
|
72
|
+
|| value.length > maximum
|
|
73
|
+
|| (validate && !validate(value)))
|
|
74
|
+
throw invalidPayload(field);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function timestamp(source, field) {
|
|
78
|
+
const value = stringField(source, field, 64);
|
|
79
|
+
if (Number.isNaN(Date.parse(value)) || new Date(value).toISOString() !== value) {
|
|
80
|
+
throw invalidPayload(field);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function nonNegativeInteger(source, field) {
|
|
85
|
+
const value = source[field];
|
|
86
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
87
|
+
throw invalidPayload(field);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function positiveInteger(source, field, maximum) {
|
|
92
|
+
const value = nonNegativeInteger(source, field);
|
|
93
|
+
if (value < 1 || (maximum !== undefined && value > maximum)) {
|
|
94
|
+
throw invalidPayload(field);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function exactTrue(source, field) {
|
|
99
|
+
if (source[field] !== true)
|
|
100
|
+
throw invalidPayload(field);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function sha256(source, field) {
|
|
104
|
+
return stringField(source, field, 64, value => SHA256_PATTERN.test(value));
|
|
105
|
+
}
|
|
106
|
+
function assertSerializedLimit(value, maximum, field) {
|
|
107
|
+
let serialized;
|
|
108
|
+
try {
|
|
109
|
+
serialized = JSON.stringify(value);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw invalidPayload(field);
|
|
113
|
+
}
|
|
114
|
+
if (!hasUtf8ByteLengthAtMost(serialized, maximum))
|
|
115
|
+
throw invalidPayload(field);
|
|
116
|
+
}
|
|
117
|
+
function decodeComparisonMember(value) {
|
|
118
|
+
const source = exactRecord(value, 'comparison.members', [
|
|
119
|
+
'activatedAt',
|
|
120
|
+
'createdAt',
|
|
121
|
+
'displayName',
|
|
122
|
+
'memberId',
|
|
123
|
+
'personalRef',
|
|
124
|
+
'role',
|
|
125
|
+
'status',
|
|
126
|
+
]);
|
|
127
|
+
const memberId = stringField(source, 'memberId', 64, isCollabMemberId);
|
|
128
|
+
const role = source.role;
|
|
129
|
+
if (role !== 'manager' && role !== 'member')
|
|
130
|
+
throw invalidPayload('role');
|
|
131
|
+
if (source.status !== 'active')
|
|
132
|
+
throw invalidPayload('status');
|
|
133
|
+
const personalRef = stringField(source, 'personalRef', COLLAB_LIMITS.maxRepositoryPathUtf16);
|
|
134
|
+
if (personalRef !== collabMemberRef(memberId))
|
|
135
|
+
throw invalidPayload('personalRef');
|
|
136
|
+
return {
|
|
137
|
+
activatedAt: timestamp(source, 'activatedAt'),
|
|
138
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
139
|
+
displayName: stringField(source, 'displayName', COLLAB_LIMITS.maxMemberDisplayNameUtf16),
|
|
140
|
+
memberId,
|
|
141
|
+
personalRef,
|
|
142
|
+
role,
|
|
143
|
+
status: 'active',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function decodeComparison(value) {
|
|
147
|
+
const source = exactRecord(value, 'comparison', [
|
|
148
|
+
'mainOid',
|
|
149
|
+
'mainRef',
|
|
150
|
+
'managerSetGeneration',
|
|
151
|
+
'members',
|
|
152
|
+
'projectCreatedAt',
|
|
153
|
+
'projectId',
|
|
154
|
+
'projectName',
|
|
155
|
+
'sourceCaFingerprint',
|
|
156
|
+
'sourceEventSequence',
|
|
157
|
+
'sourceHostMemberId',
|
|
158
|
+
]);
|
|
159
|
+
if (!Array.isArray(source.members) || source.members.length !== 2) {
|
|
160
|
+
throw invalidPayload('members');
|
|
161
|
+
}
|
|
162
|
+
const members = source.members.map(decodeComparisonMember);
|
|
163
|
+
if (members[0].memberId.localeCompare(members[1].memberId, 'en-US') >= 0
|
|
164
|
+
|| !members.some(member => member.role === 'manager'))
|
|
165
|
+
throw invalidPayload('members');
|
|
166
|
+
const sourceHostMemberId = stringField(source, 'sourceHostMemberId', 64, isCollabMemberId);
|
|
167
|
+
if (!members.some(member => member.memberId === sourceHostMemberId)) {
|
|
168
|
+
throw invalidPayload('sourceHostMemberId');
|
|
169
|
+
}
|
|
170
|
+
if (source.mainRef !== COLLAB_MAIN_REF)
|
|
171
|
+
throw invalidPayload('mainRef');
|
|
172
|
+
return {
|
|
173
|
+
mainOid: stringField(source, 'mainOid', 64, isCollabGitOid),
|
|
174
|
+
mainRef: COLLAB_MAIN_REF,
|
|
175
|
+
managerSetGeneration: nonNegativeInteger(source, 'managerSetGeneration'),
|
|
176
|
+
members,
|
|
177
|
+
projectCreatedAt: timestamp(source, 'projectCreatedAt'),
|
|
178
|
+
projectId: stringField(source, 'projectId', 64, isCollabProjectId),
|
|
179
|
+
projectName: stringField(source, 'projectName', COLLAB_LIMITS.maxProjectNameUtf16),
|
|
180
|
+
sourceCaFingerprint: stringField(source, 'sourceCaFingerprint', 64, candidate => SHA256_PATTERN.test(candidate)),
|
|
181
|
+
sourceEventSequence: nonNegativeInteger(source, 'sourceEventSequence'),
|
|
182
|
+
sourceHostMemberId,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function decodeSourceEligibility(value) {
|
|
186
|
+
const keys = [
|
|
187
|
+
'liveInvitations',
|
|
188
|
+
'nonActiveMemberships',
|
|
189
|
+
'nonterminalAcceptOperations',
|
|
190
|
+
'nonterminalHostTransfers',
|
|
191
|
+
'nonterminalManagerOffers',
|
|
192
|
+
'requestComments',
|
|
193
|
+
'requests',
|
|
194
|
+
'terminalProjectTransitions',
|
|
195
|
+
'ticketComments',
|
|
196
|
+
'ticketMentions',
|
|
197
|
+
'ticketRelations',
|
|
198
|
+
'tickets',
|
|
199
|
+
];
|
|
200
|
+
const source = exactRecord(value, 'sourceEligibility', keys);
|
|
201
|
+
if (keys.some(key => source[key] !== 0))
|
|
202
|
+
throw invalidPayload('sourceEligibility');
|
|
203
|
+
return {
|
|
204
|
+
liveInvitations: 0,
|
|
205
|
+
nonActiveMemberships: 0,
|
|
206
|
+
nonterminalAcceptOperations: 0,
|
|
207
|
+
nonterminalHostTransfers: 0,
|
|
208
|
+
nonterminalManagerOffers: 0,
|
|
209
|
+
requestComments: 0,
|
|
210
|
+
requests: 0,
|
|
211
|
+
terminalProjectTransitions: 0,
|
|
212
|
+
ticketComments: 0,
|
|
213
|
+
ticketMentions: 0,
|
|
214
|
+
ticketRelations: 0,
|
|
215
|
+
tickets: 0,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function decodeGit(value, comparison) {
|
|
219
|
+
const source = exactRecord(value, 'git', ['bundle', 'objectFormat', 'refs']);
|
|
220
|
+
if (source.objectFormat !== 'sha1' && source.objectFormat !== 'sha256') {
|
|
221
|
+
throw invalidPayload('objectFormat');
|
|
222
|
+
}
|
|
223
|
+
const objectFormat = source.objectFormat;
|
|
224
|
+
const bundleSource = exactRecord(source.bundle, 'bundle', ['byteCount', 'sha256']);
|
|
225
|
+
if (!Array.isArray(source.refs) || source.refs.length !== 3)
|
|
226
|
+
throw invalidPayload('refs');
|
|
227
|
+
const refs = source.refs.map((item) => {
|
|
228
|
+
const ref = exactRecord(item, 'refs', ['name', 'oid']);
|
|
229
|
+
const oid = stringField(ref, 'oid', 64, isCollabGitOid);
|
|
230
|
+
if (oid.length !== (objectFormat === 'sha1' ? 40 : 64))
|
|
231
|
+
throw invalidPayload('oid');
|
|
232
|
+
return {
|
|
233
|
+
name: stringField(ref, 'name', COLLAB_LIMITS.maxRepositoryPathUtf16),
|
|
234
|
+
oid,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
if (refs.some((item, index) => (index > 0 && refs[index - 1].name.localeCompare(item.name, 'en-US') >= 0)))
|
|
238
|
+
throw invalidPayload('refs');
|
|
239
|
+
const expectedNames = [
|
|
240
|
+
COLLAB_MAIN_REF,
|
|
241
|
+
...comparison.members.map(member => member.personalRef),
|
|
242
|
+
].sort((left, right) => left.localeCompare(right, 'en-US'));
|
|
243
|
+
if (refs.some((item, index) => item.name !== expectedNames[index])
|
|
244
|
+
|| refs.find(item => item.name === COLLAB_MAIN_REF)?.oid !== comparison.mainOid)
|
|
245
|
+
throw invalidPayload('refs');
|
|
246
|
+
return {
|
|
247
|
+
bundle: {
|
|
248
|
+
byteCount: positiveInteger(bundleSource, 'byteCount', COLLAB_CLOUD_BINDING_LIMITS.maxDevelopmentBootstrapGitBundleBytes),
|
|
249
|
+
sha256: sha256(bundleSource, 'sha256'),
|
|
250
|
+
},
|
|
251
|
+
objectFormat,
|
|
252
|
+
refs,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export function decodeDevelopmentBootstrapManifest(value) {
|
|
256
|
+
assertSerializedLimit(value, COLLAB_CLOUD_BINDING_LIMITS.maxDevelopmentBootstrapManifestUtf8Bytes, 'manifest');
|
|
257
|
+
const source = exactRecord(value, 'manifest', [
|
|
258
|
+
'attemptId',
|
|
259
|
+
'comparison',
|
|
260
|
+
'createdAt',
|
|
261
|
+
'git',
|
|
262
|
+
'manifestSchemaVersion',
|
|
263
|
+
'protocolVersion',
|
|
264
|
+
'sourceEligibility',
|
|
265
|
+
]);
|
|
266
|
+
if (source.manifestSchemaVersion !== DEVELOPMENT_BOOTSTRAP_MANIFEST_SCHEMA_VERSION
|
|
267
|
+
|| source.protocolVersion !== COLLAB_PROTOCOL_VERSION)
|
|
268
|
+
throw invalidPayload('manifestVersion');
|
|
269
|
+
const comparison = decodeComparison(source.comparison);
|
|
270
|
+
return {
|
|
271
|
+
attemptId: stringField(source, 'attemptId', 128, isCollabOpaqueId),
|
|
272
|
+
comparison,
|
|
273
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
274
|
+
git: decodeGit(source.git, comparison),
|
|
275
|
+
manifestSchemaVersion: DEVELOPMENT_BOOTSTRAP_MANIFEST_SCHEMA_VERSION,
|
|
276
|
+
protocolVersion: COLLAB_PROTOCOL_VERSION,
|
|
277
|
+
sourceEligibility: decodeSourceEligibility(source.sourceEligibility),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
export function encodeDevelopmentBootstrapManifestCanonicalJson(value) {
|
|
281
|
+
return JSON.stringify(decodeDevelopmentBootstrapManifest(value));
|
|
282
|
+
}
|
|
283
|
+
function decodeClientReadiness(value) {
|
|
284
|
+
const keys = [
|
|
285
|
+
'cleanupSettled',
|
|
286
|
+
'collabGitChildrenDrained',
|
|
287
|
+
'conflictRecoverySettled',
|
|
288
|
+
'hostTransferSettled',
|
|
289
|
+
'joinSettled',
|
|
290
|
+
'leaveSettled',
|
|
291
|
+
'managerResponsibilitySettled',
|
|
292
|
+
'projectOperationQueueDrained',
|
|
293
|
+
'projectSetupSettled',
|
|
294
|
+
'projectWorkSessionClosed',
|
|
295
|
+
'publishSettled',
|
|
296
|
+
'reconciliationSettled',
|
|
297
|
+
'reconnectSettled',
|
|
298
|
+
'repositoryIdentityExact',
|
|
299
|
+
'retirementSettled',
|
|
300
|
+
];
|
|
301
|
+
const source = exactRecord(value, 'clientReadiness', keys);
|
|
302
|
+
const result = Object.fromEntries(keys.map(key => [key, exactTrue(source, key)]));
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
function decodeHostStopAttestation(value, attemptId, projectId) {
|
|
306
|
+
const source = exactRecord(value, 'hostStopAttestation', [
|
|
307
|
+
'attemptId',
|
|
308
|
+
'autoStartDisabled',
|
|
309
|
+
'fenceDurable',
|
|
310
|
+
'fenceId',
|
|
311
|
+
'hostStopped',
|
|
312
|
+
'manifestSha256',
|
|
313
|
+
'projectId',
|
|
314
|
+
'resourcesDrained',
|
|
315
|
+
'routeUnregistered',
|
|
316
|
+
'stoppedAt',
|
|
317
|
+
]);
|
|
318
|
+
const decodedAttemptId = stringField(source, 'attemptId', 128, isCollabOpaqueId);
|
|
319
|
+
const decodedProjectId = stringField(source, 'projectId', 64, isCollabProjectId);
|
|
320
|
+
if (decodedAttemptId !== attemptId || decodedProjectId !== projectId) {
|
|
321
|
+
throw invalidPayload('hostStopAttestation');
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
attemptId: decodedAttemptId,
|
|
325
|
+
autoStartDisabled: exactTrue(source, 'autoStartDisabled'),
|
|
326
|
+
fenceDurable: exactTrue(source, 'fenceDurable'),
|
|
327
|
+
fenceId: stringField(source, 'fenceId', 128, isCollabOpaqueId),
|
|
328
|
+
hostStopped: exactTrue(source, 'hostStopped'),
|
|
329
|
+
manifestSha256: sha256(source, 'manifestSha256'),
|
|
330
|
+
projectId: decodedProjectId,
|
|
331
|
+
resourcesDrained: exactTrue(source, 'resourcesDrained'),
|
|
332
|
+
routeUnregistered: exactTrue(source, 'routeUnregistered'),
|
|
333
|
+
stoppedAt: timestamp(source, 'stoppedAt'),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
export function decodeDevelopmentBootstrapReport(value) {
|
|
337
|
+
assertSerializedLimit(value, COLLAB_CLOUD_BINDING_LIMITS.maxDevelopmentBootstrapReportUtf8Bytes, 'report');
|
|
338
|
+
const source = exactRecordWithOptional(value, 'report', [
|
|
339
|
+
'attemptId',
|
|
340
|
+
'capturedAt',
|
|
341
|
+
'clientReadiness',
|
|
342
|
+
'comparison',
|
|
343
|
+
'observedPersonalRefOid',
|
|
344
|
+
'reporterMemberId',
|
|
345
|
+
], ['hostStopAttestation']);
|
|
346
|
+
const attemptId = stringField(source, 'attemptId', 128, isCollabOpaqueId);
|
|
347
|
+
const comparison = decodeComparison(source.comparison);
|
|
348
|
+
const reporterMemberId = stringField(source, 'reporterMemberId', 64, isCollabMemberId);
|
|
349
|
+
if (!comparison.members.some(member => member.memberId === reporterMemberId)) {
|
|
350
|
+
throw invalidPayload('reporterMemberId');
|
|
351
|
+
}
|
|
352
|
+
const isHost = reporterMemberId === comparison.sourceHostMemberId;
|
|
353
|
+
if (isHost !== Object.hasOwn(source, 'hostStopAttestation')) {
|
|
354
|
+
throw invalidPayload('hostStopAttestation');
|
|
355
|
+
}
|
|
356
|
+
const hostStopAttestation = isHost
|
|
357
|
+
? decodeHostStopAttestation(source.hostStopAttestation, attemptId, comparison.projectId)
|
|
358
|
+
: undefined;
|
|
359
|
+
return {
|
|
360
|
+
attemptId,
|
|
361
|
+
capturedAt: timestamp(source, 'capturedAt'),
|
|
362
|
+
clientReadiness: decodeClientReadiness(source.clientReadiness),
|
|
363
|
+
comparison,
|
|
364
|
+
...(hostStopAttestation ? { hostStopAttestation } : {}),
|
|
365
|
+
observedPersonalRefOid: stringField(source, 'observedPersonalRefOid', 64, isCollabGitOid),
|
|
366
|
+
reporterMemberId,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function decodeActivationResult(value) {
|
|
370
|
+
const source = exactRecord(value, 'activationResult', [
|
|
371
|
+
'activatedAt',
|
|
372
|
+
'activationOperationId',
|
|
373
|
+
'placementGeneration',
|
|
374
|
+
'projectId',
|
|
375
|
+
]);
|
|
376
|
+
return {
|
|
377
|
+
activatedAt: timestamp(source, 'activatedAt'),
|
|
378
|
+
activationOperationId: stringField(source, 'activationOperationId', 128, isCollabOpaqueId),
|
|
379
|
+
placementGeneration: positiveInteger(source, 'placementGeneration'),
|
|
380
|
+
projectId: stringField(source, 'projectId', 64, isCollabProjectId),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function decodeAttemptStatus(value) {
|
|
384
|
+
const source = exactRecordWithOptional(value, 'attempt', [
|
|
385
|
+
'attemptId',
|
|
386
|
+
'bundleState',
|
|
387
|
+
'createdAt',
|
|
388
|
+
'expiresAt',
|
|
389
|
+
'manifestSha256',
|
|
390
|
+
'projectId',
|
|
391
|
+
'reporterMemberIds',
|
|
392
|
+
'state',
|
|
393
|
+
], ['activationPhase', 'activationResult', 'cancellationPhase']);
|
|
394
|
+
if (typeof source.state !== 'string' || !ATTEMPT_STATE_SET.has(source.state)) {
|
|
395
|
+
throw invalidPayload('state');
|
|
396
|
+
}
|
|
397
|
+
if (source.bundleState !== 'missing'
|
|
398
|
+
&& source.bundleState !== 'uploaded'
|
|
399
|
+
&& source.bundleState !== 'validated')
|
|
400
|
+
throw invalidPayload('bundleState');
|
|
401
|
+
if (!Array.isArray(source.reporterMemberIds) || source.reporterMemberIds.length > 2) {
|
|
402
|
+
throw invalidPayload('reporterMemberIds');
|
|
403
|
+
}
|
|
404
|
+
const reporterMemberIds = source.reporterMemberIds.map((item) => {
|
|
405
|
+
if (!isCollabMemberId(item))
|
|
406
|
+
throw invalidPayload('reporterMemberIds');
|
|
407
|
+
return item;
|
|
408
|
+
});
|
|
409
|
+
if (reporterMemberIds.some((item, index) => (index > 0 && reporterMemberIds[index - 1].localeCompare(item, 'en-US') >= 0)))
|
|
410
|
+
throw invalidPayload('reporterMemberIds');
|
|
411
|
+
const activationPhase = source.activationPhase;
|
|
412
|
+
const cancellationPhase = source.cancellationPhase;
|
|
413
|
+
if (activationPhase !== undefined
|
|
414
|
+
&& (typeof activationPhase !== 'string' || !ACTIVATION_PHASE_SET.has(activationPhase)))
|
|
415
|
+
throw invalidPayload('activationPhase');
|
|
416
|
+
if (cancellationPhase !== undefined
|
|
417
|
+
&& (typeof cancellationPhase !== 'string' || !CANCELLATION_PHASE_SET.has(cancellationPhase)))
|
|
418
|
+
throw invalidPayload('cancellationPhase');
|
|
419
|
+
const activationResult = source.activationResult === undefined
|
|
420
|
+
? undefined
|
|
421
|
+
: decodeActivationResult(source.activationResult);
|
|
422
|
+
const projectId = stringField(source, 'projectId', 64, isCollabProjectId);
|
|
423
|
+
if (source.state === 'activated'
|
|
424
|
+
&& ((activationPhase !== 'activated' && activationPhase !== 'completed')
|
|
425
|
+
|| activationResult === undefined))
|
|
426
|
+
throw invalidPayload('activationResult');
|
|
427
|
+
if (source.state !== 'activated'
|
|
428
|
+
&& activationResult !== undefined)
|
|
429
|
+
throw invalidPayload('activationResult');
|
|
430
|
+
if (activationPhase !== undefined && cancellationPhase !== undefined) {
|
|
431
|
+
throw invalidPayload('attemptPhase');
|
|
432
|
+
}
|
|
433
|
+
if (activationResult !== undefined && activationResult.projectId !== projectId) {
|
|
434
|
+
throw invalidPayload('activationResult');
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
...(activationPhase
|
|
438
|
+
? { activationPhase: activationPhase }
|
|
439
|
+
: {}),
|
|
440
|
+
...(activationResult ? { activationResult } : {}),
|
|
441
|
+
attemptId: stringField(source, 'attemptId', 128, isCollabOpaqueId),
|
|
442
|
+
bundleState: source.bundleState,
|
|
443
|
+
...(cancellationPhase
|
|
444
|
+
? { cancellationPhase: cancellationPhase }
|
|
445
|
+
: {}),
|
|
446
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
447
|
+
expiresAt: timestamp(source, 'expiresAt'),
|
|
448
|
+
manifestSha256: sha256(source, 'manifestSha256'),
|
|
449
|
+
projectId,
|
|
450
|
+
reporterMemberIds,
|
|
451
|
+
state: source.state,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function decodeAttemptOnlyRequest(value) {
|
|
455
|
+
const source = exactRecord(value, 'request', ['attemptId']);
|
|
456
|
+
return { attemptId: stringField(source, 'attemptId', 128, isCollabOpaqueId) };
|
|
457
|
+
}
|
|
458
|
+
function decodeOperationRequest(operation, value) {
|
|
459
|
+
switch (operation) {
|
|
460
|
+
case 'beginDevelopmentBootstrap': {
|
|
461
|
+
const source = exactRecord(value, 'request', ['manifest']);
|
|
462
|
+
return { manifest: decodeDevelopmentBootstrapManifest(source.manifest) };
|
|
463
|
+
}
|
|
464
|
+
case 'submitDevelopmentBootstrapReport': {
|
|
465
|
+
const source = exactRecord(value, 'request', ['attemptId', 'report']);
|
|
466
|
+
const attemptId = stringField(source, 'attemptId', 128, isCollabOpaqueId);
|
|
467
|
+
const report = decodeDevelopmentBootstrapReport(source.report);
|
|
468
|
+
if (report.attemptId !== attemptId)
|
|
469
|
+
throw invalidPayload('attemptId');
|
|
470
|
+
return { attemptId, report };
|
|
471
|
+
}
|
|
472
|
+
case 'getDevelopmentBootstrap':
|
|
473
|
+
case 'cancelDevelopmentBootstrap':
|
|
474
|
+
return decodeAttemptOnlyRequest(value);
|
|
475
|
+
case 'activateDevelopmentBootstrap': {
|
|
476
|
+
const source = exactRecord(value, 'request', ['attemptId', 'manifestSha256']);
|
|
477
|
+
return {
|
|
478
|
+
attemptId: stringField(source, 'attemptId', 128, isCollabOpaqueId),
|
|
479
|
+
manifestSha256: sha256(source, 'manifestSha256'),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
case 'putDevelopmentBootstrapGitBundle': {
|
|
483
|
+
const source = exactRecord(value, 'request', [
|
|
484
|
+
'attemptId',
|
|
485
|
+
'byteCount',
|
|
486
|
+
'contentEncoding',
|
|
487
|
+
'contentType',
|
|
488
|
+
'sha256',
|
|
489
|
+
]);
|
|
490
|
+
if (source.contentEncoding !== 'identity'
|
|
491
|
+
|| source.contentType !== 'application/x-git-bundle')
|
|
492
|
+
throw invalidPayload('contentType');
|
|
493
|
+
return {
|
|
494
|
+
attemptId: stringField(source, 'attemptId', 128, isCollabOpaqueId),
|
|
495
|
+
byteCount: positiveInteger(source, 'byteCount', COLLAB_CLOUD_BINDING_LIMITS.maxDevelopmentBootstrapGitBundleBytes),
|
|
496
|
+
contentEncoding: 'identity',
|
|
497
|
+
contentType: 'application/x-git-bundle',
|
|
498
|
+
sha256: sha256(source, 'sha256'),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function decodeRequestResult(operation, value) {
|
|
504
|
+
try {
|
|
505
|
+
return { status: 'ok', value: decodeOperationRequest(operation, value) };
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
return {
|
|
509
|
+
status: 'invalid',
|
|
510
|
+
error: error instanceof CollabError ? error : invalidPayload('request'),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function codec(operation) {
|
|
515
|
+
return Object.freeze({
|
|
516
|
+
decodeRequest: (value) => decodeRequestResult(operation, value),
|
|
517
|
+
decodeResponse: decodeAttemptStatus,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
export const DEVELOPMENT_BOOTSTRAP_OPERATION_CODECS = Object.freeze({
|
|
521
|
+
beginDevelopmentBootstrap: codec('beginDevelopmentBootstrap'),
|
|
522
|
+
submitDevelopmentBootstrapReport: codec('submitDevelopmentBootstrapReport'),
|
|
523
|
+
getDevelopmentBootstrap: codec('getDevelopmentBootstrap'),
|
|
524
|
+
activateDevelopmentBootstrap: codec('activateDevelopmentBootstrap'),
|
|
525
|
+
cancelDevelopmentBootstrap: codec('cancelDevelopmentBootstrap'),
|
|
526
|
+
putDevelopmentBootstrapGitBundle: codec('putDevelopmentBootstrapGitBundle'),
|
|
527
|
+
});
|
|
528
|
+
export function developmentBootstrapOperationCodec(operation) {
|
|
529
|
+
const selected = DEVELOPMENT_BOOTSTRAP_OPERATION_CODECS[operation];
|
|
530
|
+
if (!selected) {
|
|
531
|
+
throw new CollabError({
|
|
532
|
+
code: 'operation-failed',
|
|
533
|
+
safeContext: { reason: 'bootstrap-operation-codec-missing' },
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return selected;
|
|
537
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { COLLAB_LIMITS, COLLAB_MAIN_REF, COLLAB_MEMBER_REF_PREFIX, COLLAB_PROTOCOL_VERSION, } from './CollabConstants.mjs';
|
|
2
|
+
export { COLLAB_AUTHORITY_TRANSFER_CANCELLABLE_PHASES, COLLAB_AUTHORITY_TRANSFER_CANCELLATION_PHASES, COLLAB_AUTHORITY_TRANSFER_OPERATIONS, COLLAB_CLOUD_TO_LAN_TRANSFER_PHASES, COLLAB_LAN_TO_CLOUD_TRANSFER_PHASES, decodeCollabAuthorityRelinquishmentProof, decodeCollabAuthorityTransferOperationRequest, decodeCollabAuthorityTransferOperationResponse, decodeCollabAuthorityTransferProposal, decodeCollabAuthorityTransferStatus, decodeCollabTransferredMembershipClaim, decodeCollabTransferredMembershipClaimBatch, decodeCollabTransferredMembershipClaimCustodyReceipt, decodeCollabTransferredMembershipRedemptionReceipt, encodeCollabAuthorityRelinquishmentProofSigningInput, encodeCollabTransferredMembershipClaimBatchDigestInput, encodeCollabTransferredMembershipRedemptionReceiptSigningInput, } from './CollabAuthorityTransfer.mjs';
|
|
3
|
+
export { COLLAB_CHECKPOINT_ARTIFACT_LIMITS, COLLAB_CHECKPOINT_BACKUP_RECORD_KINDS, COLLAB_CHECKPOINT_PORTABLE_RECORD_KINDS, COLLAB_CHECKPOINT_PROFILES, COLLAB_PROJECT_CHECKPOINT_ARTIFACTS, COLLAB_PROJECT_CHECKPOINT_MANIFEST_SCHEMA_VERSION, COLLAB_PROJECT_COORDINATION_FORMAT_VERSION, COLLAB_PROTECTED_CLAIM_ENVELOPE_LIMITS, COLLAB_PROTECTED_CLAIM_ENVELOPE_VERSION, decodeCollabProjectCheckpointCoordinationNdjson, decodeCollabProjectCheckpointManifest, encodeCollabProjectCheckpointCoordinationNdjson, encodeCollabProjectCheckpointManifestCanonicalJson, encodeCollabProjectCheckpointManifestDigestInput, encodeCollabProtectedClaimAssociatedData, validateCollabProjectCheckpointConsistency, } from './CollabProjectCheckpoint.mjs';
|
|
4
|
+
export { COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION, COLLAB_PROJECT_BACKUP_RECORD_KINDS, collabProjectBackupIdempotencyRecordId, decodeCollabProjectBackupCheckpointCoordinationNdjson, decodeCollabProjectBackupCheckpointManifest, encodeCollabProjectBackupCheckpointCoordinationNdjson, encodeCollabProjectBackupCheckpointManifestCanonicalJson, encodeCollabProjectBackupCheckpointManifestDigestInput, validateCollabProjectBackupCheckpointConsistency, } from './CollabProjectBackupCheckpoint.mjs';
|
|
5
|
+
export { COLLAB_PROJECT_RETIREMENT_OPERATIONS, COLLAB_PROJECT_RETIREMENT_RESULT_KINDS, decodeCollabProjectRetirementAcknowledgement, decodeCollabProjectRetirementOperationRequest, decodeCollabProjectRetirementOperationResponse, decodeCollabProjectRetirementRequest, decodeCollabProjectRetirementResult, } from './CollabProjectRetirement.mjs';
|
|
6
|
+
export { COLLAB_CLOUD_BINDING_LIMITS, COLLAB_CLOUD_BINDING_VERSION, COLLAB_CLOUD_CAPABILITIES, COLLAB_CLOUD_CAPABILITY_DOCUMENT_SCHEMA_VERSION, COLLAB_CLOUD_JSON_OPERATIONS, collabCloudAuthorityTransferArtifactRoute, collabCloudCapabilityDocument, collabCloudCapabilitySupported, collabCloudCapabilitiesRoute, collabCloudErrorEnvelope, collabCloudGitRoute, collabCloudProjectCheckpointExportArtifactRoute, collabCloudProjectCheckpointExportRoute, collabCloudProjectEventsRoute, collabCloudProjectOperationRoute, collabCloudSuccessEnvelope, collabDevelopmentBootstrapRoute, decodeCollabCloudCapabilityDocument, decodeCollabCloudErrorEnvelope, decodeCollabCloudProjectCheckpointExportStatus, decodeCollabCloudSuccessEnvelope, matchCollabCloudRoute, } from './CollabCloudBinding.mjs';
|
|
7
|
+
export { DEVELOPMENT_BOOTSTRAP_ACTIVATION_PHASES, DEVELOPMENT_BOOTSTRAP_ATTEMPT_STATES, DEVELOPMENT_BOOTSTRAP_CANCELLATION_PHASES, DEVELOPMENT_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, DEVELOPMENT_BOOTSTRAP_OPERATIONS, DEVELOPMENT_BOOTSTRAP_OPERATION_CODECS, decodeDevelopmentBootstrapManifest, decodeDevelopmentBootstrapReport, developmentBootstrapOperationCodec, encodeDevelopmentBootstrapManifestCanonicalJson, } from './DevelopmentBootstrap.mjs';
|
|
8
|
+
export { COLLAB_CLOUD_PROJECT_SNAPSHOT_CODEC, decodeCollabCloudProjectSnapshot, } from './CollabCloudProjectSnapshot.mjs';
|
|
9
|
+
export { COLLAB_CLOUD_EVENT_KINDS, decodeCollabCloudProjectEventMessage, } from './CollabCloudProjectEvent.mjs';
|
|
10
|
+
export { COLLAB_CONTROL_OPERATION_CODECS, collabControlOperationCodec, } from './CollabControlOperationCodecs.mjs';
|
|
11
|
+
export { COLLAB_ERROR_CODES, CollabError, collabErrorGroup, sanitizeCollabDiagnosticContext, } from './CollabError.mjs';
|
|
12
|
+
export { parseCollabMemberMentions } from './CollabMemberMentionParser.mjs';
|
|
13
|
+
export { decodeCollabProtocolEnvelope } from './CollabProtocol.mjs';
|
|
14
|
+
export { parseCollabTicketReferences, scanCollabTicketReferences, } from './CollabTicketReferenceParser.mjs';
|
|
15
|
+
export { isCollabGitOid, isCollabMemberId, isCollabOpaqueId, isCollabProjectId, } from './CollabValidation.mjs';
|
|
16
|
+
export { collabMemberRef } from './types.mjs';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { COLLAB_MEMBER_REF_PREFIX, } from './CollabConstants.mjs';
|
|
2
|
+
import { isCollabMemberId } from './CollabValidation.mjs';
|
|
3
|
+
export function collabMemberRef(memberId) {
|
|
4
|
+
if (!isCollabMemberId(memberId)) {
|
|
5
|
+
throw new RangeError('Invalid Collab member ID');
|
|
6
|
+
}
|
|
7
|
+
return `${COLLAB_MEMBER_REF_PREFIX}${memberId}`;
|
|
8
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudian-collab/protocol",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Canonical Collab wire and Cloud binding contract for Claudian clients and servers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,10 +21,13 @@
|
|
|
21
21
|
"packageManager": "npm@11.13.0",
|
|
22
22
|
"type": "commonjs",
|
|
23
23
|
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/esm/index.mjs",
|
|
24
25
|
"types": "./dist/index.d.ts",
|
|
25
26
|
"exports": {
|
|
26
27
|
".": {
|
|
27
28
|
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/esm/index.mjs",
|
|
30
|
+
"require": "./dist/index.js",
|
|
28
31
|
"default": "./dist/index.js"
|
|
29
32
|
}
|
|
30
33
|
},
|
|
@@ -34,7 +37,7 @@
|
|
|
34
37
|
],
|
|
35
38
|
"sideEffects": false,
|
|
36
39
|
"scripts": {
|
|
37
|
-
"build": "npm run clean && tsc -p tsconfig.json",
|
|
40
|
+
"build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json && node scripts/finalize-esm.mjs",
|
|
38
41
|
"check:compatibility": "node scripts/check-compatibility.mjs",
|
|
39
42
|
"check:migration-provenance": "node scripts/check-migration-provenance.mjs --verify-standalone",
|
|
40
43
|
"clean": "node scripts/clean.mjs",
|