@dxos/credentials 0.5.0 → 0.5.1-main.0ba1ecb

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.
Files changed (34) hide show
  1. package/dist/lib/browser/index.mjs +755 -167
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +729 -144
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/credentials/credential-factory.d.ts +3 -1
  8. package/dist/types/src/credentials/credential-factory.d.ts.map +1 -1
  9. package/dist/types/src/credentials/credential-generator.d.ts +18 -2
  10. package/dist/types/src/credentials/credential-generator.d.ts.map +1 -1
  11. package/dist/types/src/credentials/signing.d.ts.map +1 -1
  12. package/dist/types/src/credentials/verifier.d.ts +1 -1
  13. package/dist/types/src/credentials/verifier.d.ts.map +1 -1
  14. package/dist/types/src/graph/credential-graph.d.ts +152 -0
  15. package/dist/types/src/graph/credential-graph.d.ts.map +1 -0
  16. package/dist/types/src/state-machine/member-state-machine.d.ts +17 -12
  17. package/dist/types/src/state-machine/member-state-machine.d.ts.map +1 -1
  18. package/dist/types/src/state-machine/member-state-machine.test.d.ts +2 -0
  19. package/dist/types/src/state-machine/member-state-machine.test.d.ts.map +1 -0
  20. package/dist/types/src/state-machine/space-state-machine.d.ts +3 -3
  21. package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
  22. package/package.json +16 -16
  23. package/project.json +3 -0
  24. package/src/credentials/credential-factory.ts +9 -1
  25. package/src/credentials/credential-generator.ts +35 -2
  26. package/src/credentials/signing.ts +1 -0
  27. package/src/credentials/verifier.test.ts +25 -0
  28. package/src/credentials/verifier.ts +5 -1
  29. package/src/graph/credential-graph.ts +557 -0
  30. package/src/state-machine/invitation-state-machine.test.ts +1 -1
  31. package/src/state-machine/member-state-machine.test.ts +580 -0
  32. package/src/state-machine/member-state-machine.ts +111 -53
  33. package/src/state-machine/space-state-machine.test.ts +20 -16
  34. package/src/state-machine/space-state-machine.ts +12 -33
@@ -0,0 +1,580 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+ import { expect } from 'chai';
5
+
6
+ import { Keyring } from '@dxos/keyring';
7
+ import { PublicKey } from '@dxos/keys';
8
+ import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
9
+ import { describe, test } from '@dxos/test';
10
+ import { range } from '@dxos/util';
11
+
12
+ import { MemberStateMachine } from './member-state-machine';
13
+ import { createAdmissionCredentials, createCredentialSignerWithKey } from '../credentials';
14
+
15
+ const keyring = new Keyring();
16
+
17
+ describe('MemberStateMachine', () => {
18
+ const genesisFeedKey: PublicKey = PublicKey.random();
19
+
20
+ let spaceKey: PublicKey;
21
+ beforeEach(async () => {
22
+ spaceKey = await keyring.createKey();
23
+ });
24
+
25
+ test('compatibility with old credentials (no parent references)', async () => {
26
+ const stateMachine = createStateMachine();
27
+ const [A, B, C, D, E] = await createPeers(5);
28
+ await admit(stateMachine, spaceKey, A, []);
29
+ await admit(stateMachine, A, B, []);
30
+ await admit(stateMachine, B, C, []);
31
+ await admit(stateMachine, D, E, []);
32
+ expectOwnerAndAdmins(stateMachine, [A, B, C]);
33
+ expectRoles(stateMachine, [
34
+ [D, SpaceMember.Role.REMOVED],
35
+ [E, SpaceMember.Role.REMOVED],
36
+ ]);
37
+ });
38
+
39
+ test('first member is the owner if no explicit credential', async () => {
40
+ const stateMachine = createStateMachine();
41
+ const [A, B] = await createPeers(2);
42
+ await admit(stateMachine, spaceKey, A, [], SpaceMember.Role.ADMIN);
43
+ await admit(stateMachine, spaceKey, B, [], SpaceMember.Role.ADMIN);
44
+ expectOwnerAndAdmins(stateMachine, [A, B]);
45
+ });
46
+
47
+ test('owner removal forbidden', async () => {
48
+ const stateMachine = createStateMachine();
49
+ const [A, B] = await createPeers(2);
50
+ const spaceCreated = await createSpace(stateMachine, A);
51
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
52
+ await remove(stateMachine, B, A, [aAdmitB]);
53
+ expectOwnerAndAdmins(stateMachine, [A, B]);
54
+ });
55
+
56
+ test('implicit owner removal forbidden', async () => {
57
+ const stateMachine = createStateMachine();
58
+ const [A, B] = await createPeers(2);
59
+ await admit(stateMachine, spaceKey, A, [], SpaceMember.Role.ADMIN);
60
+ const admitB = await admit(stateMachine, spaceKey, B, [], SpaceMember.Role.ADMIN);
61
+ await remove(stateMachine, B, A, [admitB]);
62
+ expectOwnerAndAdmins(stateMachine, [A, B]);
63
+ });
64
+
65
+ test('non-member credential ignored', async () => {
66
+ const stateMachine = createStateMachine();
67
+ const [A, B, C] = await createPeers(3);
68
+ const spaceCreated = await createSpace(stateMachine, A);
69
+ await admit(stateMachine, B, C, [spaceCreated]);
70
+ expectRoles(stateMachine, [
71
+ [A, SpaceMember.Role.OWNER],
72
+ [B, SpaceMember.Role.REMOVED],
73
+ [C, SpaceMember.Role.REMOVED],
74
+ ]);
75
+ });
76
+
77
+ test('non-admin admission ignored', async () => {
78
+ const stateMachine = createStateMachine();
79
+ const [A, B, C] = await createPeers(3);
80
+ const spaceCreated = await createSpace(stateMachine, A);
81
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated], SpaceMember.Role.EDITOR);
82
+ await admit(stateMachine, B, C, [aAdmitB]);
83
+ expectRoles(stateMachine, [
84
+ [A, SpaceMember.Role.OWNER],
85
+ [B, SpaceMember.Role.EDITOR],
86
+ [C, SpaceMember.Role.REMOVED],
87
+ ]);
88
+ });
89
+
90
+ test('own role update forbidden', async () => {
91
+ const stateMachine = createStateMachine();
92
+ const [A, B] = await createPeers(2);
93
+ const spaceCreated = await createSpace(stateMachine, A);
94
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated], SpaceMember.Role.ADMIN);
95
+ await updateRole(stateMachine, B, B, SpaceMember.Role.EDITOR, [aAdmitB]);
96
+ expectRoles(stateMachine, [
97
+ [A, SpaceMember.Role.OWNER],
98
+ [B, SpaceMember.Role.ADMIN],
99
+ ]);
100
+ });
101
+
102
+ /*
103
+ +-------+
104
+ +--->|Admit A|<----------+
105
+ | +-------+ | |
106
+ +-------+ +-------+ +-------+
107
+ |Admit B| |Admit C| |Admit D|
108
+ +-------+ +-------+ +-------+
109
+ */
110
+ test('leaf fork is merged', async () => {
111
+ const stateMachine = createStateMachine();
112
+ const actors = await createPeers(4);
113
+ const spaceCreated = await createSpace(stateMachine, actors[0]);
114
+ for (const guest of actors.slice(1)) {
115
+ await admit(stateMachine, actors[0], guest, [spaceCreated]);
116
+ }
117
+ expectOwnerAndAdmins(stateMachine, actors);
118
+ });
119
+
120
+ /*
121
+ +-------+
122
+ +--->|Admit A|<----------+
123
+ | +-------+ | |
124
+ +-+-----+ +-------+ +-------+
125
+ |Admit B| |Admit C| |Admit D|
126
+ +-------+ +-------+ +-------+
127
+ ^ ^ ^
128
+ | +---------+ |
129
+ +----|D admit E|--------+
130
+ +---------+
131
+ */
132
+ test('middle fork is merged', async () => {
133
+ const stateMachine = createStateMachine();
134
+ const actors = await createPeers(5);
135
+ const [A, B, C, D, E] = actors;
136
+ const spaceCreated = await createSpace(stateMachine, A);
137
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
138
+ const aAdmitC = await admit(stateMachine, A, C, [spaceCreated]);
139
+ const aAdmitD = await admit(stateMachine, A, D, [spaceCreated]);
140
+ await admit(stateMachine, D, E, [aAdmitB, aAdmitC, aAdmitD]);
141
+ expectOwnerAndAdmins(stateMachine, actors);
142
+ });
143
+
144
+ /*
145
+ +---------+
146
+ | Admit A |
147
+ +---------+
148
+ +---^-------+
149
+ | A admit B |
150
+ +-----------+
151
+ +----^------+
152
+ +->+ B admit C +<-+
153
+ | +-----------+ |
154
+ ++--------+ +---+-----+
155
+ |C admit E| |C admit D|
156
+ +---------+ +---------+
157
+ +----^-----+
158
+ |B remove C|
159
+ +----------+
160
+ */
161
+ test('D is removed because invite was concurrent with C removal, but E is a member', async () => {
162
+ const stateMachine = createStateMachine();
163
+ const [A, B, C, D, E] = await createPeers(5);
164
+ const spaceCreated = await createSpace(stateMachine, A);
165
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
166
+ const bAdmitC = await admit(stateMachine, A, C, [aAdmitB]);
167
+ const cAdmitE = await admit(stateMachine, C, E, [bAdmitC]);
168
+ await admit(stateMachine, C, D, [bAdmitC]);
169
+ await remove(stateMachine, B, C, [cAdmitE]);
170
+ expectRoles(stateMachine, [
171
+ [B, SpaceMember.Role.ADMIN],
172
+ [E, SpaceMember.Role.ADMIN],
173
+ [C, SpaceMember.Role.REMOVED],
174
+ [D, SpaceMember.Role.REMOVED],
175
+ ]);
176
+ });
177
+
178
+ /*
179
+ +---------+
180
+ | Admit A |
181
+ +---------+
182
+ +---^-----+
183
+ |A admit B|
184
+ +---------+
185
+ +---^-----+
186
+ +--->|A admit C|<----------+
187
+ | +---------+ | |
188
+ +----------++----------+ +----+----+
189
+ |B remove C|| C admit E| |A admit E|
190
+ +----------++----------+ +---------+
191
+ +--^-------+
192
+ |A admit D |
193
+ +----------+
194
+ */
195
+ test('E was invited in 2 branches one of which was discarded - E is a member', async () => {
196
+ const stateMachine = createStateMachine();
197
+ const [A, B, C, D, E] = await createPeers(5);
198
+ const spaceCreated = await createSpace(stateMachine, A);
199
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
200
+ const aAdmitC = await admit(stateMachine, A, C, [aAdmitB]);
201
+ const bRemoveC = await remove(stateMachine, B, C, [aAdmitC]);
202
+ await admit(stateMachine, A, D, [bRemoveC]);
203
+ await admit(stateMachine, A, E, [aAdmitC]);
204
+ await admit(stateMachine, C, E, [aAdmitC]);
205
+ expectRoles(stateMachine, [
206
+ [A, SpaceMember.Role.OWNER],
207
+ [B, SpaceMember.Role.ADMIN],
208
+ [D, SpaceMember.Role.ADMIN],
209
+ [E, SpaceMember.Role.ADMIN],
210
+ [C, SpaceMember.Role.REMOVED],
211
+ ]);
212
+ });
213
+
214
+ /*
215
+ +---------+
216
+ | Admit A |
217
+ +---------+
218
+ +-----------+
219
+ | A admit B |
220
+ +-----------+
221
+ +-----------+
222
+ +->+ B admit C +<-+
223
+ | +-----------+ |
224
+ ++--------+ +---+-----+
225
+ |C admit E| |C admit D|
226
+ +---------+ +---------+
227
+ +----------+ +---------+
228
+ |B remove C| |A admit F|
229
+ +----------+ +---------+
230
+ */
231
+ test('D invite was discarded, but F joined', async () => {
232
+ const stateMachine = createStateMachine();
233
+ const [A, B, C, D, E, F] = await createPeers(6);
234
+ const spaceCreated = await createSpace(stateMachine, A);
235
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
236
+ const bAdmitC = await admit(stateMachine, A, C, [aAdmitB]);
237
+ const cAdmitE = await admit(stateMachine, C, E, [bAdmitC]);
238
+ const cAdmitD = await admit(stateMachine, C, D, [bAdmitC]);
239
+ await remove(stateMachine, B, C, [cAdmitE]);
240
+ await admit(stateMachine, A, F, [cAdmitD]);
241
+ expectRoles(stateMachine, [
242
+ [B, SpaceMember.Role.ADMIN],
243
+ [E, SpaceMember.Role.ADMIN],
244
+ [F, SpaceMember.Role.ADMIN],
245
+ [C, SpaceMember.Role.REMOVED],
246
+ [D, SpaceMember.Role.REMOVED],
247
+ ]);
248
+ });
249
+
250
+ /*
251
+ +---------+
252
+ +--->| Admit A |<---+
253
+ | +---------+ |
254
+ +---------+ +---------+
255
+ +->|A admit C|<+ +>|A admit B|<-+
256
+ | +---------+ | | +---------+ |
257
+ +---------+ +---------+ +---------+ +----------+
258
+ |C admit E| |C admit D| |B admit F| |A remove C|
259
+ +---------+ +---------+ +---------+ +----------+
260
+ +----^-----+
261
+ | B admit G|
262
+ +----------+
263
+ +----^-----+
264
+ | G admit H|
265
+ +----------+
266
+ */
267
+ test('E and D are not members because C was removed in a parallel longer branch', async () => {
268
+ const stateMachine = createStateMachine();
269
+ const [A, B, C, D, E, F, G, H] = await createPeers(8);
270
+ const spaceCreated = await createSpace(stateMachine, A);
271
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
272
+ const aAdmitC = await admit(stateMachine, A, C, [spaceCreated]);
273
+ await admit(stateMachine, C, E, [aAdmitC]);
274
+ await admit(stateMachine, C, D, [aAdmitC]);
275
+ await admit(stateMachine, B, F, [aAdmitB]);
276
+ const aRemoveC = await remove(stateMachine, A, C, [aAdmitB]);
277
+ const bAdmitG = await admit(stateMachine, B, G, [aRemoveC]);
278
+ await admit(stateMachine, G, H, [bAdmitG]);
279
+ expectRoles(stateMachine, [
280
+ [A, SpaceMember.Role.OWNER],
281
+ [B, SpaceMember.Role.ADMIN],
282
+ [C, SpaceMember.Role.REMOVED],
283
+ [D, SpaceMember.Role.REMOVED],
284
+ [F, SpaceMember.Role.ADMIN],
285
+ [E, SpaceMember.Role.REMOVED],
286
+ ]);
287
+ });
288
+
289
+ /*
290
+ +---------+
291
+ | Admit A |
292
+ +---------+
293
+ +---------+
294
+ |A admit B|
295
+ +---------+
296
+ +---------+
297
+ +--->|A admit C|<-------------+
298
+ | +---------+ | |
299
+ +--+-------+ +---------+ +---------+
300
+ |B remove C| |C admit E| |B admit F|
301
+ +----------+ +---------+ +--+------+
302
+ +--^-------+ +-----^----+ ^
303
+ |A admit D | |E remove F| |
304
+ +----------+ +----------+ |
305
+ +--^-------+ +-^---------+
306
+ |D admit G | | E admit H |
307
+ +----------+ +-----------+
308
+ +--^-------+
309
+ |G admit I |
310
+ +----------+
311
+ */
312
+ test('F is not a member in subbranch, but becomes a member when branch with C removal is discovered', async () => {
313
+ const stateMachine = createStateMachine();
314
+ const [A, B, C, D, E, F, G, H, I] = await createPeers(9);
315
+ const spaceCreated = await createSpace(stateMachine, A);
316
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
317
+ const aAdmitC = await admit(stateMachine, A, C, [aAdmitB]);
318
+ const bAdmitF = await admit(stateMachine, B, F, [aAdmitC]);
319
+ const cAdmitE = await admit(stateMachine, C, E, [aAdmitC]);
320
+ const eRemoveF = await remove(stateMachine, E, F, [cAdmitE]);
321
+ await admit(stateMachine, E, H, [eRemoveF, bAdmitF]);
322
+ expectRoles(stateMachine, [
323
+ [A, SpaceMember.Role.OWNER],
324
+ [B, SpaceMember.Role.ADMIN],
325
+ [C, SpaceMember.Role.ADMIN],
326
+ [E, SpaceMember.Role.ADMIN],
327
+ [F, SpaceMember.Role.REMOVED],
328
+ ]);
329
+ const bRemoveC = await remove(stateMachine, B, C, [aAdmitC]);
330
+ const aAdmitD = await admit(stateMachine, A, D, [bRemoveC]);
331
+ const dAdmitG = await admit(stateMachine, D, G, [aAdmitD]);
332
+ await admit(stateMachine, G, I, [dAdmitG]);
333
+ expectRoles(stateMachine, [
334
+ [A, SpaceMember.Role.OWNER],
335
+ [B, SpaceMember.Role.ADMIN],
336
+ [F, SpaceMember.Role.ADMIN],
337
+ [E, SpaceMember.Role.REMOVED],
338
+ [C, SpaceMember.Role.REMOVED],
339
+ ]);
340
+ });
341
+
342
+ /*
343
+ +---------+
344
+ | Admit A |
345
+ +---------+
346
+ +---------+
347
+ |A admit B|
348
+ +---------+
349
+ +---------+
350
+ +--->|A admit C|<-------------+
351
+ | +---------+ | |
352
+ +--+-------+ +---------+ +---------+
353
+ |B remove C| |C admit E| |A admit F|
354
+ +----------+ +---------+ +--+------+
355
+ +--^-------+ +-----^----+
356
+ |A admit D | |E remove F|
357
+ +----------+ +----------+
358
+ +--^-------+
359
+ |D admit G |
360
+ +----------+
361
+ +--^-------+
362
+ |G admit H |
363
+ +----------+
364
+ */
365
+ test('F is a member because concurrent branch gets removed by a longer branch', async () => {
366
+ const stateMachine = createStateMachine();
367
+ const [A, B, C, D, E, F, G, H] = await createPeers(8);
368
+ const spaceCreated = await createSpace(stateMachine, A);
369
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
370
+ const aAdmitC = await admit(stateMachine, A, C, [aAdmitB]);
371
+ const cAdmitE = await admit(stateMachine, C, E, [aAdmitC]);
372
+ await remove(stateMachine, E, F, [cAdmitE]);
373
+ await admit(stateMachine, A, F, [aAdmitC]);
374
+ const bRemoveC = await remove(stateMachine, B, C, [aAdmitC]);
375
+ const aAdmitD = await admit(stateMachine, A, D, [bRemoveC]);
376
+ const dAdmitG = await admit(stateMachine, D, G, [aAdmitD]);
377
+ await admit(stateMachine, G, H, [dAdmitG]);
378
+ expectRoles(stateMachine, [
379
+ [A, SpaceMember.Role.OWNER],
380
+ [B, SpaceMember.Role.ADMIN],
381
+ [F, SpaceMember.Role.ADMIN],
382
+ [E, SpaceMember.Role.REMOVED],
383
+ [C, SpaceMember.Role.REMOVED],
384
+ ]);
385
+ });
386
+
387
+ /*
388
+ +-------+
389
+ +--->|Admit A|<--+
390
+ | +-------+ |
391
+ | |
392
+ +---------+ +---------+
393
+ |A admit B| |A admit C|<----+
394
+ +---------+ +---------+ |
395
+ ^ ^ |
396
+ +---------+ | +---------+
397
+ |B admit E| | |C admit D|
398
+ +---------+ | +---------+
399
+ ^ +----------+
400
+ +----+E remove C|
401
+ +----------+
402
+ */
403
+ test('D is removed because C was removed in a longer branch', async () => {
404
+ const stateMachine = createStateMachine();
405
+ const [A, B, C, D, E] = await createPeers(5);
406
+ const spaceCreated = await createSpace(stateMachine, A);
407
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
408
+ const aAdmitC = await admit(stateMachine, A, C, [spaceCreated]);
409
+ const bAdmitE = await admit(stateMachine, B, E, [aAdmitB]);
410
+ await admit(stateMachine, C, D, [aAdmitC]);
411
+ await remove(stateMachine, E, C, [bAdmitE, aAdmitC]);
412
+ expectRoles(stateMachine, [
413
+ [A, SpaceMember.Role.OWNER],
414
+ [B, SpaceMember.Role.ADMIN],
415
+ [E, SpaceMember.Role.ADMIN],
416
+ [C, SpaceMember.Role.REMOVED],
417
+ [D, SpaceMember.Role.REMOVED],
418
+ ]);
419
+ });
420
+
421
+ /**
422
+ +-------+
423
+ |Admit A|
424
+ +-------+
425
+ +--------^+ +^--------+
426
+ |A admit B| |A admit C|
427
+ +-----^---+ +-----^---+
428
+ | |
429
+ +-------------+
430
+ | |
431
+ +----------+ +---------+
432
+ |B remove C| |C admit D|
433
+ +----------+ +---------+
434
+ */
435
+ test('D is removed because D auth was authorized by a credential revoked in a concurrent branch', async () => {
436
+ const stateMachine = createStateMachine();
437
+ const [A, B, C, D] = await createPeers(4);
438
+ const spaceCreated = await createSpace(stateMachine, A);
439
+ const aAdmitB = await admit(stateMachine, A, B, [spaceCreated]);
440
+ const aAdmitC = await admit(stateMachine, A, C, [spaceCreated]);
441
+ await admit(stateMachine, C, D, [aAdmitB, aAdmitC]);
442
+ await remove(stateMachine, B, C, [aAdmitB, aAdmitC]);
443
+ expectRoles(stateMachine, [
444
+ [A, SpaceMember.Role.OWNER],
445
+ [B, SpaceMember.Role.ADMIN],
446
+ [C, SpaceMember.Role.REMOVED],
447
+ [D, SpaceMember.Role.REMOVED],
448
+ ]);
449
+ });
450
+
451
+ /*
452
+ +-------+
453
+ |Admit A|
454
+ +-------+
455
+ ^ ^
456
+ +---------+ | | +---------+
457
+ |A admit B|--------+ +---|A admit G|<----------+
458
+ +---------+ +---------+ |
459
+ +--------^+ +^--------+ +-----^---+ +---------+
460
+ |B admit C| |B admit D| |G admit H| |G admit K|
461
+ +---------+ +---------+ +---------+ +---------+
462
+ +--------^+ ^ ^ +---------+ +-----^---+ +----^----+
463
+ |C admit E| | +---|D admit F| |H admit I| |K admit L|
464
+ +---------+ | +---------+ +---------+ +---------+
465
+ ^ +----------+ ^ +-----^---+
466
+ +----+E remove D| | |I admit J|
467
+ +----------+ | +---------+
468
+ +----------+ ^
469
+ |J remove K|---+
470
+ +----------+
471
+ */
472
+ test('combined case ordering tolerance', async () => {
473
+ const [A, B, C, D, E, F, G, H, I, J, K, L] = await createPeers(12);
474
+ const addLeftBranch = async (stateMachine: MemberStateMachine, genesis: PublicKey) => {
475
+ const aAdmitB = await admit(stateMachine, A, B, [genesis]);
476
+ const bAdmitC = await admit(stateMachine, B, C, [aAdmitB]);
477
+ const cAdmitE = await admit(stateMachine, C, E, [bAdmitC]);
478
+ const bAdmitD = await admit(stateMachine, B, D, [aAdmitB]);
479
+ const dAdmitF = await admit(stateMachine, D, F, [bAdmitD]);
480
+ await remove(stateMachine, E, D, [bAdmitD, cAdmitE]);
481
+ return dAdmitF;
482
+ };
483
+ const addRightBranch = async (stateMachine: MemberStateMachine, genesis: PublicKey) => {
484
+ const aAdmitG = await admit(stateMachine, A, G, [genesis]);
485
+ const gAdmitK = await admit(stateMachine, G, K, [aAdmitG]);
486
+ await admit(stateMachine, K, L, [gAdmitK]);
487
+ const gAdmitH = await admit(stateMachine, G, H, [aAdmitG]);
488
+ const hAdmitI = await admit(stateMachine, H, I, [gAdmitH]);
489
+ return admit(stateMachine, I, J, [hAdmitI]);
490
+ };
491
+ const branches = [addLeftBranch, addRightBranch];
492
+ for (const firstBranch of [0, 1]) {
493
+ const stateMachine = createStateMachine();
494
+ const spaceCreated = await createSpace(stateMachine, A);
495
+ const connectorParent1 = await branches[firstBranch](stateMachine, spaceCreated);
496
+ const connectorParent2 = await branches[1 - firstBranch](stateMachine, spaceCreated);
497
+ await remove(stateMachine, J, K, [connectorParent1, connectorParent2]);
498
+ expectOwnerAndAdmins(stateMachine, [A, B, C, E, G, H, I, J]);
499
+ expectRoles(stateMachine, [
500
+ [L, SpaceMember.Role.REMOVED],
501
+ [K, SpaceMember.Role.REMOVED],
502
+ [D, SpaceMember.Role.REMOVED],
503
+ [F, SpaceMember.Role.REMOVED],
504
+ ]);
505
+ }
506
+ });
507
+
508
+ const createSpace = async (stateMachine: MemberStateMachine, creator: PublicKey): Promise<PublicKey> => {
509
+ return admit(stateMachine, spaceKey, creator, [], SpaceMember.Role.OWNER);
510
+ };
511
+
512
+ const updateRole = (
513
+ stateMachine: MemberStateMachine,
514
+ host: PublicKey,
515
+ guest: PublicKey,
516
+ role?: SpaceMember.Role,
517
+ parents?: PublicKey[],
518
+ ) => {
519
+ return admit(stateMachine, host, guest, parents, role);
520
+ };
521
+
522
+ const admit = async (
523
+ stateMachine: MemberStateMachine,
524
+ host: PublicKey,
525
+ guest: PublicKey,
526
+ parents?: PublicKey[],
527
+ role?: SpaceMember.Role,
528
+ ): Promise<PublicKey> => {
529
+ const signer = createCredentialSignerWithKey(keyring, host);
530
+ const feedMessage = await createAdmissionCredentials(
531
+ signer,
532
+ guest,
533
+ spaceKey,
534
+ genesisFeedKey,
535
+ role ?? SpaceMember.Role.ADMIN,
536
+ parents,
537
+ );
538
+ const credential = feedMessage[0].credential!.credential;
539
+ await stateMachine.process(credential);
540
+ return feedMessage[0].credential!.credential.id!;
541
+ };
542
+
543
+ const remove = async (
544
+ stateMachine: MemberStateMachine,
545
+ remover: PublicKey,
546
+ removed: PublicKey,
547
+ parents?: PublicKey[],
548
+ ): Promise<PublicKey> => {
549
+ const signer = createCredentialSignerWithKey(keyring, remover);
550
+ const feedMessage = await createAdmissionCredentials(
551
+ signer,
552
+ removed,
553
+ spaceKey,
554
+ genesisFeedKey,
555
+ SpaceMember.Role.REMOVED,
556
+ parents,
557
+ );
558
+ const credential = feedMessage[0].credential!.credential;
559
+ await stateMachine.process(credential);
560
+ return feedMessage[0].credential!.credential.id!;
561
+ };
562
+
563
+ const createPeers = (count: number) => Promise.all(range(count).map(() => keyring.createKey()));
564
+
565
+ const createStateMachine = () => new MemberStateMachine(spaceKey);
566
+
567
+ const expectOwnerAndAdmins = (stateMachine: MemberStateMachine, ownerAndAdmins: PublicKey[]) => {
568
+ expectRoles(stateMachine, [
569
+ [ownerAndAdmins[0], SpaceMember.Role.OWNER],
570
+ ...ownerAndAdmins.slice(1).map((key) => [key, SpaceMember.Role.ADMIN] as [PublicKey, SpaceMember.Role]),
571
+ ]);
572
+ };
573
+
574
+ const expectRoles = (stateMachine: MemberStateMachine, expectation: Array<[PublicKey, SpaceMember.Role]>) => {
575
+ for (let i = 0; i < expectation.length; i++) {
576
+ const expected = expectation[i];
577
+ expect(stateMachine.getRole(expected[0]), `failed at index ${i}`).to.eq(expected[1]);
578
+ }
579
+ };
580
+ });