@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
@@ -39,6 +39,7 @@ __export(node_exports, {
39
39
  canonicalStringify: () => canonicalStringify,
40
40
  checkCredentialType: () => checkCredentialType,
41
41
  createAdmissionCredentials: () => createAdmissionCredentials,
42
+ createCancelDelegatedSpaceInvitationCredential: () => createCancelDelegatedSpaceInvitationCredential,
42
43
  createCredential: () => createCredential,
43
44
  createCredentialMessage: () => createCredentialMessage,
44
45
  createCredentialSignerWithChain: () => createCredentialSignerWithChain,
@@ -87,13 +88,18 @@ var import_util4 = require("@dxos/util");
87
88
  var import_invariant3 = require("@dxos/invariant");
88
89
  var import_keys6 = require("@dxos/keys");
89
90
  var import_log2 = require("@dxos/log");
91
+ var import_credentials3 = require("@dxos/protocols/proto/dxos/halo/credentials");
90
92
  var import_util5 = require("@dxos/util");
91
- var import_async2 = require("@dxos/async");
92
93
  var import_invariant4 = require("@dxos/invariant");
93
94
  var import_keys7 = require("@dxos/keys");
94
95
  var import_log3 = require("@dxos/log");
95
96
  var import_util6 = require("@dxos/util");
97
+ var import_async2 = require("@dxos/async");
98
+ var import_invariant5 = require("@dxos/invariant");
99
+ var import_keys8 = require("@dxos/keys");
96
100
  var import_log4 = require("@dxos/log");
101
+ var import_util7 = require("@dxos/util");
102
+ var import_log5 = require("@dxos/log");
97
103
  var defaultSecretProvider = async () => Buffer.from("0000");
98
104
  var defaultSecretValidator = async (invitation, secret) => true;
99
105
  var generatePasscode = (length = 4) => {
@@ -178,6 +184,9 @@ var checkCredentialType = (credential, type) => credential.subject.assertion["@t
178
184
  var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
179
185
  var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
180
186
  var verifyCredential = async (credential) => {
187
+ if (credential.parentCredentialIds?.length === 0) {
188
+ delete credential.parentCredentialIds;
189
+ }
181
190
  if (!credential.issuer.equals(credential.proof.signer)) {
182
191
  if (!credential.proof.chain) {
183
192
  return {
@@ -240,10 +249,10 @@ var verifyChain = async (chain, authority, subject) => {
240
249
  };
241
250
  };
242
251
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
243
- var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
252
+ var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
244
253
  (0, import_invariant.invariant)(assertion["@type"], "Invalid assertion.", {
245
254
  F: __dxlog_file,
246
- L: 45,
255
+ L: 48,
247
256
  S: void 0,
248
257
  A: [
249
258
  "assertion['@type']",
@@ -252,7 +261,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
252
261
  });
253
262
  (0, import_invariant.invariant)(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
254
263
  F: __dxlog_file,
255
- L: 46,
264
+ L: 49,
256
265
  S: void 0,
257
266
  A: [
258
267
  "!!signingKey === !!chain",
@@ -263,7 +272,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
263
272
  const result = await verifyChain(chain, issuer, signingKey);
264
273
  (0, import_invariant.invariant)(result.kind === "pass", "Invalid chain.", {
265
274
  F: __dxlog_file,
266
- L: 49,
275
+ L: 52,
267
276
  S: void 0,
268
277
  A: [
269
278
  "result.kind === 'pass'",
@@ -286,6 +295,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
286
295
  nonce
287
296
  }
288
297
  };
298
+ if ((parentCredentialIds?.length ?? 0) > 0) {
299
+ credential.parentCredentialIds = parentCredentialIds;
300
+ }
289
301
  const signedPayload = getCredentialProofPayload(credential);
290
302
  credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
291
303
  if (chain) {
@@ -302,12 +314,13 @@ var createCredentialMessage = (credential) => {
302
314
  };
303
315
  var createCredentialSignerWithKey = (signer, issuer) => ({
304
316
  getIssuer: () => issuer,
305
- createCredential: ({ subject, assertion, nonce }) => createCredential({
317
+ createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
306
318
  signer,
307
319
  issuer,
308
320
  subject,
309
321
  assertion,
310
- nonce
322
+ nonce,
323
+ parentCredentialIds
311
324
  })
312
325
  });
313
326
  var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
@@ -371,7 +384,7 @@ var CredentialGenerator = class {
371
384
  assertion: {
372
385
  "@type": "dxos.halo.credentials.SpaceMember",
373
386
  spaceKey,
374
- role: import_credentials.SpaceMember.Role.MEMBER,
387
+ role: import_credentials.SpaceMember.Role.EDITOR,
375
388
  genesisFeedKey
376
389
  }
377
390
  }),
@@ -466,14 +479,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
466
479
  credential
467
480
  }));
468
481
  };
469
- var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, profile, invitationCredentialId) => {
482
+ var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = import_credentials.SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId) => {
470
483
  const credentials = await Promise.all([
471
484
  await signer.createCredential({
472
485
  subject: identityKey,
486
+ parentCredentialIds: membershipChainHeads,
473
487
  assertion: {
474
488
  "@type": "dxos.halo.credentials.SpaceMember",
475
489
  spaceKey,
476
- role: import_credentials.SpaceMember.Role.ADMIN,
490
+ role,
477
491
  profile,
478
492
  genesisFeedKey,
479
493
  invitationCredentialId
@@ -506,6 +520,20 @@ var createDelegatedSpaceInvitationCredential = async (signer, subject, invitatio
506
520
  }
507
521
  };
508
522
  };
523
+ var createCancelDelegatedSpaceInvitationCredential = async (signer, subject, invitationCredentialId) => {
524
+ const credential = await signer.createCredential({
525
+ subject,
526
+ assertion: {
527
+ "@type": "dxos.halo.invitations.CancelDelegatedInvitation",
528
+ credentialId: invitationCredentialId
529
+ }
530
+ });
531
+ return {
532
+ credential: {
533
+ credential
534
+ }
535
+ };
536
+ };
509
537
  var signPresentation = async ({ presentation, signer, signerKey, chain, nonce }) => {
510
538
  const proof = {
511
539
  type: SIGNATURE_TYPE_ED25519,
@@ -720,21 +748,574 @@ var InvitationStateMachine = class {
720
748
  }
721
749
  }
722
750
  };
723
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
751
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
752
+ var CredentialGraph = class {
753
+ constructor(_stateHandler) {
754
+ this._stateHandler = _stateHandler;
755
+ this._vertexIdGenerator = 1;
756
+ this._root = {
757
+ id: -1,
758
+ parents: [],
759
+ children: []
760
+ };
761
+ this._sentinel = {
762
+ id: -2,
763
+ parents: [],
764
+ children: []
765
+ };
766
+ this._vertexByCredentialId = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
767
+ this._subjectToVertex = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
768
+ this._subjectToState = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
769
+ this.onSubjectStateChanged = new import_util6.Callback();
770
+ }
771
+ getSubjectState(subjectId) {
772
+ return this._subjectToState.get(subjectId);
773
+ }
774
+ getState() {
775
+ return this._subjectToState;
776
+ }
777
+ getLeafIds() {
778
+ return this._sentinel.parents.map((v) => v.credential.id);
779
+ }
780
+ getGlobalStateScope() {
781
+ return {
782
+ state: this._subjectToVertex
783
+ };
784
+ }
785
+ addVertex(credential, assertion) {
786
+ const newVertex = {
787
+ id: this._vertexIdGenerator++,
788
+ credential,
789
+ assertion,
790
+ parents: [],
791
+ children: []
792
+ };
793
+ this._vertexByCredentialId.set(credential.id, newVertex);
794
+ const parentIds = credential.parentCredentialIds ?? [];
795
+ if (parentIds.length === 0) {
796
+ this._root.children.push(newVertex);
797
+ newVertex.parents.push(this._root);
798
+ } else {
799
+ for (const parentId of parentIds) {
800
+ const parentVertex = this._vertexByCredentialId.get(parentId);
801
+ if (parentVertex == null) {
802
+ import_log3.log.error("credential skipped because of the unknown parent", {
803
+ credential,
804
+ parentId
805
+ }, {
806
+ F: __dxlog_file3,
807
+ L: 72,
808
+ S: this,
809
+ C: (f, a) => f(...a)
810
+ });
811
+ continue;
812
+ }
813
+ parentVertex.children.push(newVertex);
814
+ newVertex.parents.push(parentVertex);
815
+ this._removeSentinelConnection(parentVertex);
816
+ }
817
+ }
818
+ newVertex.children.push(this._sentinel);
819
+ this._sentinel.parents.push(newVertex);
820
+ return this._onVertexInserted(newVertex);
821
+ }
822
+ _removeSentinelConnection(vertex) {
823
+ const sentinelIdx = vertex.children.indexOf(this._sentinel);
824
+ if (sentinelIdx >= 0) {
825
+ vertex.children.splice(sentinelIdx, 1);
826
+ const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
827
+ (0, import_invariant4.invariant)(vertexInSentinelIdx >= 0, void 0, {
828
+ F: __dxlog_file3,
829
+ L: 90,
830
+ S: this,
831
+ A: [
832
+ "vertexInSentinelIdx >= 0",
833
+ ""
834
+ ]
835
+ });
836
+ this._sentinel.parents.splice(vertexInSentinelIdx, 1);
837
+ }
838
+ }
839
+ async _onVertexInserted(newVertex) {
840
+ const { credential, assertion } = newVertex;
841
+ (0, import_invariant4.invariant)(credential, void 0, {
842
+ F: __dxlog_file3,
843
+ L: 97,
844
+ S: this,
845
+ A: [
846
+ "credential",
847
+ ""
848
+ ]
849
+ });
850
+ let changedSubjects = [];
851
+ const isUpdateAppliedOnTopOfThePreviousState = this._sentinel.parents.length === 1;
852
+ if (isUpdateAppliedOnTopOfThePreviousState) {
853
+ const subjectId = credential.subject.id;
854
+ if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
855
+ const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
856
+ const prevSubjectState = this._subjectToState.get(subjectId);
857
+ this._subjectToState.set(subjectId, newSubjectState);
858
+ this._subjectToVertex.set(subjectId, newVertex);
859
+ if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) {
860
+ changedSubjects.push(newSubjectState);
861
+ }
862
+ }
863
+ } else {
864
+ changedSubjects = this._recomputeState();
865
+ }
866
+ if (changedSubjects.length > 0) {
867
+ await this.onSubjectStateChanged.callIfSet(changedSubjects);
868
+ }
869
+ }
870
+ /**
871
+ * DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
872
+ * Continue after all paths leading to a merge point converge by merging their states.
873
+ * In case of a concurrent update paths are replayed taking into account the state set
874
+ * by the winning branch.
875
+ */
876
+ _recomputeState() {
877
+ const pendingPaths = /* @__PURE__ */ new Map();
878
+ const paths = [
879
+ this._createRootPath()
880
+ ];
881
+ let lastPath = null;
882
+ while (lastPath == null) {
883
+ const path = paths.pop();
884
+ (0, import_log3.log)("visit vertex", {
885
+ id: path.head.id
886
+ }, {
887
+ F: __dxlog_file3,
888
+ L: 132,
889
+ S: this,
890
+ C: (f, a) => f(...a)
891
+ });
892
+ this._updatePathState(path);
893
+ const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
894
+ if (convergedPaths == null) {
895
+ (0, import_log3.log)("waiting for other paths", void 0, {
896
+ F: __dxlog_file3,
897
+ L: 136,
898
+ S: this,
899
+ C: (f, a) => f(...a)
900
+ });
901
+ continue;
902
+ }
903
+ const mergeResult = this._mergePaths(convergedPaths);
904
+ if (mergeResult.type === "replay_required") {
905
+ this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
906
+ continue;
907
+ }
908
+ const merged = mergeResult.path;
909
+ if (merged.head.children.length === 0) {
910
+ lastPath = merged;
911
+ } else if (merged.head.children.length === 1) {
912
+ merged.head = merged.head.children[0];
913
+ paths.push(merged);
914
+ } else {
915
+ this._forkTraversal(paths, merged);
916
+ }
917
+ }
918
+ if (paths.length > 0) {
919
+ import_log3.log.error("traversal finished while there were active paths", {
920
+ paths: paths.map((p) => ({
921
+ path: toChosenPath(p),
922
+ head: p.head.id
923
+ }))
924
+ }, {
925
+ F: __dxlog_file3,
926
+ L: 155,
927
+ S: this,
928
+ C: (f, a) => f(...a)
929
+ });
930
+ }
931
+ return this._setCurrentState(lastPath);
932
+ }
933
+ _replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
934
+ paths.push(...mergeResult.replay.map((path) => {
935
+ const stateOverrides = path.stateOverrides ?? new import_util6.ComplexMap(import_keys7.PublicKey.hash);
936
+ mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
937
+ return {
938
+ ...mergeResult.from,
939
+ chosenPath: path.chosenPath,
940
+ stateOverrides
941
+ };
942
+ }));
943
+ (0, import_log3.log)("replay paths", () => ({
944
+ count: paths.length,
945
+ paths: paths.map((path) => ({
946
+ from: mergeResult.from.head.id,
947
+ path: toChosenPath(path),
948
+ overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
949
+ }))
950
+ }), {
951
+ F: __dxlog_file3,
952
+ L: 175,
953
+ S: this,
954
+ C: (f, a) => f(...a)
955
+ });
956
+ const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
957
+ pendingPaths.set(convergedPaths[0].head.id, clearedPending);
958
+ }
959
+ _handleMergePoint(paths, pendingPaths, path) {
960
+ const pendingList = pendingPaths.get(path.head.id) ?? [];
961
+ pendingPaths.set(path.head.id, pendingList);
962
+ pendingList.push(path);
963
+ if (pendingList.length < path.head.parents.length) {
964
+ return null;
965
+ }
966
+ if (path.head.id === this._sentinel.id && paths.length > 0) {
967
+ (0, import_log3.log)("waiting for all the active paths to converge on the sentinel", void 0, {
968
+ F: __dxlog_file3,
969
+ L: 199,
970
+ S: this,
971
+ C: (f, a) => f(...a)
972
+ });
973
+ return null;
974
+ }
975
+ pendingPaths.delete(path.head.id);
976
+ return pendingList;
977
+ }
978
+ _updatePathState(path) {
979
+ const headCredential = path.head.credential;
980
+ if (headCredential == null) {
981
+ return;
982
+ }
983
+ const updatedSubject = headCredential.subject.id;
984
+ path.credentials.add(headCredential.id);
985
+ let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
986
+ if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
987
+ const globalState = this.getGlobalStateScope();
988
+ isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
989
+ }
990
+ if (isUpdateAllowed) {
991
+ path.forkChangedSubjects.add(updatedSubject);
992
+ path.forkIssuers.add(headCredential.issuer);
993
+ path.state.set(updatedSubject, path.head);
994
+ (0, import_log3.log)("path state updated", () => ({
995
+ subject: updatedSubject,
996
+ newState: this._stateHandler.toLogString(path.head.assertion)
997
+ }), {
998
+ F: __dxlog_file3,
999
+ L: 223,
1000
+ S: this,
1001
+ C: (f, a) => f(...a)
1002
+ });
1003
+ }
1004
+ }
1005
+ _forkTraversal(paths, path) {
1006
+ const replayChoice = path.chosenPath?.[path.head.id];
1007
+ const choices = replayChoice ?? path.head.children;
1008
+ for (const choice of choices) {
1009
+ (0, import_log3.log)("edge traversal", {
1010
+ from: path.head.id,
1011
+ to: choice.id
1012
+ }, {
1013
+ F: __dxlog_file3,
1014
+ L: 234,
1015
+ S: this,
1016
+ C: (f, a) => f(...a)
1017
+ });
1018
+ const fork = {
1019
+ forkPoint: path,
1020
+ chosenPath: {
1021
+ ...path.chosenPath,
1022
+ [path.head.id]: [
1023
+ choice
1024
+ ]
1025
+ },
1026
+ head: choice,
1027
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, path.credentials),
1028
+ state: new import_util6.ComplexMap(import_keys7.PublicKey.hash, [
1029
+ ...path.state.entries()
1030
+ ]),
1031
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1032
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1033
+ stateOverrides: path.stateOverrides
1034
+ };
1035
+ paths.push(fork);
1036
+ }
1037
+ }
1038
+ /**
1039
+ * Updates the current graph state.
1040
+ * @returns changed states.
1041
+ */
1042
+ _setCurrentState(path) {
1043
+ const changedSubjects = [];
1044
+ const newStateMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1045
+ const newVertexMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1046
+ for (const [subjectKey, subjectVertex] of path.state.entries()) {
1047
+ const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
1048
+ const prevState = this._subjectToState.get(subjectKey);
1049
+ newStateMap.set(subjectKey, newState);
1050
+ newVertexMap.set(subjectKey, subjectVertex);
1051
+ if (this._stateHandler.hasStateChanged(newState, prevState)) {
1052
+ changedSubjects.push(newState);
1053
+ }
1054
+ }
1055
+ this._subjectToState = newStateMap;
1056
+ this._subjectToVertex = newVertexMap;
1057
+ return changedSubjects;
1058
+ }
1059
+ /*
1060
+ * Walk up all the fork points and return the first one present in all the paths.
1061
+ * We use local id to determine vertex position in the graph, because nodes can't
1062
+ * be inserted in the middle (between a parent and a child) and ids are monotonically increasing.
1063
+ */
1064
+ _leastCommonAncestor(paths) {
1065
+ const uniqueForkPoints = paths.reduce((acc, path) => {
1066
+ let it = path.forkPoint;
1067
+ while (it) {
1068
+ acc.set(it.head.id, it);
1069
+ it = it.forkPoint;
1070
+ }
1071
+ return acc;
1072
+ }, /* @__PURE__ */ new Map());
1073
+ let maxId = this._root.id;
1074
+ let maxState = null;
1075
+ for (const [id, state] of uniqueForkPoints.entries()) {
1076
+ const headCredential = state.head.credential;
1077
+ if (headCredential != null) {
1078
+ const isPointInEveryPath = paths.every((p) => p.credentials.has(headCredential.id));
1079
+ if (isPointInEveryPath && id > maxId) {
1080
+ maxId = id;
1081
+ maxState = state;
1082
+ }
1083
+ }
1084
+ }
1085
+ return maxState ?? this._createRootPath();
1086
+ }
1087
+ /**
1088
+ * We might be merging paths where some of them had fork points after the initial forking.
1089
+ * We need all the paths to point to the least common fork point and contain all the changes
1090
+ * that happened after it.
1091
+ */
1092
+ _moveUpToForkPoint(forkPoint, path) {
1093
+ const isForkPointInPath = path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null;
1094
+ if (isForkPointInPath) {
1095
+ return path;
1096
+ }
1097
+ if (forkPoint.head.id === path.forkPoint?.head.id) {
1098
+ return path;
1099
+ }
1100
+ let it = path.forkPoint;
1101
+ while (it.head.id !== forkPoint.head.id) {
1102
+ it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
1103
+ it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
1104
+ it = it.forkPoint;
1105
+ path.forkPoint = it;
1106
+ }
1107
+ return path;
1108
+ }
1109
+ _mergePaths(convergedPaths) {
1110
+ (0, import_invariant4.invariant)(convergedPaths.length >= 1, void 0, {
1111
+ F: __dxlog_file3,
1112
+ L: 324,
1113
+ S: this,
1114
+ A: [
1115
+ "convergedPaths.length >= 1",
1116
+ ""
1117
+ ]
1118
+ });
1119
+ if (convergedPaths.length === 1) {
1120
+ return {
1121
+ type: "merged",
1122
+ path: convergedPaths[0]
1123
+ };
1124
+ }
1125
+ const forkPoint = this._leastCommonAncestor(convergedPaths);
1126
+ (0, import_log3.log)("merging paths", () => ({
1127
+ forkPointId: forkPoint.head.id,
1128
+ pathCount: convergedPaths.length,
1129
+ forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
1130
+ }), {
1131
+ F: __dxlog_file3,
1132
+ L: 329,
1133
+ S: this,
1134
+ C: (f, a) => f(...a)
1135
+ });
1136
+ const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
1137
+ (0, import_invariant4.invariant)(forkPoint, void 0, {
1138
+ F: __dxlog_file3,
1139
+ L: 335,
1140
+ S: this,
1141
+ A: [
1142
+ "forkPoint",
1143
+ ""
1144
+ ]
1145
+ });
1146
+ const result = {
1147
+ forkPoint: forkPoint.forkPoint,
1148
+ chosenPath: {
1149
+ ...forkPoint.chosenPath,
1150
+ [forkPoint.head.id]: []
1151
+ },
1152
+ stateOverrides: forkPoint.stateOverrides,
1153
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.credentials),
1154
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkIssuers),
1155
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkChangedSubjects),
1156
+ state: forkPoint.state.mapValues((v) => v),
1157
+ head: paths[0].head
1158
+ };
1159
+ const subjectToBranch = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1160
+ for (const path of paths) {
1161
+ (0, import_log3.log)("processing a path", () => ({
1162
+ choices: toChosenPath(path),
1163
+ modified: path.forkChangedSubjects,
1164
+ forkIssuers: path.forkIssuers,
1165
+ state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
1166
+ }), {
1167
+ F: __dxlog_file3,
1168
+ L: 348,
1169
+ S: this,
1170
+ C: (f, a) => f(...a)
1171
+ });
1172
+ path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
1173
+ path.credentials.forEach((cred) => result.credentials.add(cred));
1174
+ result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
1175
+ for (const modifiedSubject of path.forkChangedSubjects) {
1176
+ const existingBranch = subjectToBranch.get(modifiedSubject);
1177
+ if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) {
1178
+ subjectToBranch.set(modifiedSubject, path);
1179
+ }
1180
+ }
1181
+ }
1182
+ const replayPaths = /* @__PURE__ */ new Set();
1183
+ const addReplayPath = replayPaths.add.bind(replayPaths);
1184
+ for (const [subject, branch] of subjectToBranch.entries()) {
1185
+ result.forkChangedSubjects.add(subject);
1186
+ const vertex = branch.state.get(subject);
1187
+ result.state.set(subject, vertex);
1188
+ (0, import_log3.log)("set subject state", () => ({
1189
+ subject,
1190
+ state: this._stateHandler.toLogString(vertex.assertion)
1191
+ }), {
1192
+ F: __dxlog_file3,
1193
+ L: 370,
1194
+ S: this,
1195
+ C: (f, a) => f(...a)
1196
+ });
1197
+ const otherPaths = paths.filter((p) => p !== branch);
1198
+ this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
1199
+ }
1200
+ if (replayPaths.size > 0) {
1201
+ return {
1202
+ type: "replay_required",
1203
+ replay: [
1204
+ ...replayPaths.values()
1205
+ ],
1206
+ from: forkPoint,
1207
+ stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
1208
+ };
1209
+ }
1210
+ return {
1211
+ type: "merged",
1212
+ path: result
1213
+ };
1214
+ }
1215
+ /**
1216
+ * A candidate credential is preferred over the existing credential if:
1217
+ * 1. It is the merge-point, because it's the last credential that was issued in awareness of all
1218
+ * the previously existing ones.
1219
+ * 2. A path where candidate was set contains existing credential in it, which means that the candidate
1220
+ * was issued after the existing credential by a legitimate issuer.
1221
+ * 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
1222
+ * 4. The path where candidate was set has more issuers than the existing path (longer branch).
1223
+ * 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
1224
+ */
1225
+ _shouldOverrideCredential(existing, candidate, modifiedSubject) {
1226
+ const candidateVertex = candidate.state.get(modifiedSubject);
1227
+ const currentVertex = existing.state.get(modifiedSubject);
1228
+ if (candidateVertex.id === currentVertex.id) {
1229
+ return false;
1230
+ }
1231
+ const mergePointId = existing.head.id;
1232
+ if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
1233
+ (0, import_log3.log)("merge point chosen to break the tie", {
1234
+ mergePointId: existing.head.id
1235
+ }, {
1236
+ F: __dxlog_file3,
1237
+ L: 408,
1238
+ S: this,
1239
+ C: (f, a) => f(...a)
1240
+ });
1241
+ return mergePointId === candidateVertex.id;
1242
+ }
1243
+ const candidateCredential = candidateVertex.credential;
1244
+ const currentCredential = currentVertex.credential;
1245
+ if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
1246
+ (0, import_log3.log)("one of the credentials was overridden in another branch", {
1247
+ current: currentVertex.id,
1248
+ candidate: candidateVertex.id
1249
+ }, {
1250
+ F: __dxlog_file3,
1251
+ L: 415,
1252
+ S: this,
1253
+ C: (f, a) => f(...a)
1254
+ });
1255
+ return candidate.credentials.has(currentCredential.id);
1256
+ }
1257
+ const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
1258
+ if (winningCredential != null) {
1259
+ return winningCredential === candidateCredential;
1260
+ }
1261
+ if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
1262
+ (0, import_log3.log)("longer issuers branch used to break the tie", {
1263
+ issuerCount: [
1264
+ existing.forkIssuers.size,
1265
+ candidate.forkIssuers.size
1266
+ ]
1267
+ }, {
1268
+ F: __dxlog_file3,
1269
+ L: 432,
1270
+ S: this,
1271
+ C: (f, a) => f(...a)
1272
+ });
1273
+ return candidate.forkIssuers.size > existing.forkIssuers.size;
1274
+ }
1275
+ (0, import_log3.log)("issuance date used to break the tie", void 0, {
1276
+ F: __dxlog_file3,
1277
+ L: 437,
1278
+ S: this,
1279
+ C: (f, a) => f(...a)
1280
+ });
1281
+ return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
1282
+ }
1283
+ _createRootPath() {
1284
+ return {
1285
+ head: this._root,
1286
+ chosenPath: {},
1287
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1288
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1289
+ state: new import_util6.ComplexMap(import_keys7.PublicKey.hash),
1290
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash)
1291
+ };
1292
+ }
1293
+ };
1294
+ var toChosenPath = (path) => {
1295
+ return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [
1296
+ k,
1297
+ vs.map((v) => v.id)
1298
+ ]));
1299
+ };
1300
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
724
1301
  var MemberStateMachine = class {
725
1302
  constructor(_spaceKey) {
726
1303
  this._spaceKey = _spaceKey;
727
- this._members = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
728
- this.onMemberAdmitted = new import_util5.Callback();
1304
+ this._memberProfiles = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
1305
+ this._hashgraph = new CredentialGraph(this);
1306
+ this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
729
1307
  }
730
1308
  get creator() {
731
- return this._creator;
1309
+ return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
732
1310
  }
733
1311
  get members() {
734
- return this._members;
1312
+ return this._hashgraph.getState();
1313
+ }
1314
+ get membershipChainHeads() {
1315
+ return this._hashgraph.getLeafIds();
735
1316
  }
736
1317
  getRole(member) {
737
- return this._members.get(member)?.assertion.role;
1318
+ return this._getRole(this._hashgraph.getGlobalStateScope(), member);
738
1319
  }
739
1320
  /**
740
1321
  * Processes the SpaceMember credential.
@@ -745,84 +1326,122 @@ var MemberStateMachine = class {
745
1326
  switch (assertion["@type"]) {
746
1327
  case "dxos.halo.credentials.SpaceMember": {
747
1328
  (0, import_invariant3.invariant)(assertion.spaceKey.equals(this._spaceKey), void 0, {
748
- F: __dxlog_file3,
749
- L: 58,
1329
+ F: __dxlog_file4,
1330
+ L: 66,
750
1331
  S: this,
751
1332
  A: [
752
1333
  "assertion.spaceKey.equals(this._spaceKey)",
753
1334
  ""
754
1335
  ]
755
1336
  });
756
- if (this._members.has(credential.subject.id)) {
757
- return;
1337
+ if (this._ownerKey == null && credential.issuer === this._spaceKey) {
1338
+ this._ownerKey = credential.subject.id;
758
1339
  }
759
- const info = {
760
- key: credential.subject.id,
761
- credential,
762
- assertion,
763
- removed: false,
764
- profile: assertion.profile
765
- };
766
- if (!this._creator) {
767
- this._creator = info;
1340
+ if (assertion.profile != null) {
1341
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
768
1342
  }
769
- this._members.set(credential.subject.id, info);
770
- (0, import_log2.log)("member added", {
771
- member: credential.subject.id,
772
- space: this._spaceKey,
773
- role: assertion.role,
774
- profile: assertion.profile
775
- }, {
776
- F: __dxlog_file3,
777
- L: 79,
778
- S: this,
779
- C: (f, a) => f(...a)
780
- });
781
- await this.onMemberAdmitted.callIfSet(info);
1343
+ await this._hashgraph.addVertex(credential, assertion);
782
1344
  break;
783
1345
  }
784
1346
  case "dxos.halo.credentials.MemberProfile": {
785
- const member = this._members.get(credential.subject.id);
786
- if (!member) {
1347
+ const member = this._hashgraph.getSubjectState(credential.subject.id);
1348
+ if (member) {
1349
+ member.profile = assertion.profile;
1350
+ } else {
787
1351
  import_log2.log.warn("Member not found", {
788
1352
  id: credential.subject.id
789
1353
  }, {
790
- F: __dxlog_file3,
791
- L: 93,
1354
+ F: __dxlog_file4,
1355
+ L: 81,
792
1356
  S: this,
793
1357
  C: (f, a) => f(...a)
794
1358
  });
795
- } else {
796
- member.profile = assertion.profile;
797
1359
  }
1360
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
798
1361
  break;
799
1362
  }
800
1363
  default:
801
1364
  throw new Error("Invalid assertion type");
802
1365
  }
803
1366
  }
804
- async onRevoked(revoked, revocation) {
805
- (0, import_invariant3.invariant)(revoked.id, void 0, {
806
- F: __dxlog_file3,
807
- L: 106,
808
- S: this,
809
- A: [
810
- "revoked.id",
811
- ""
812
- ]
813
- });
814
- for (const member of this._members.values()) {
815
- if (member.credential.id?.equals(revoked.id)) {
816
- member.removed = true;
817
- break;
1367
+ createState(credential, assertion) {
1368
+ const memberKey = credential.subject.id;
1369
+ return {
1370
+ key: memberKey,
1371
+ role: assertion.role,
1372
+ credential,
1373
+ assertion,
1374
+ profile: this._memberProfiles.get(memberKey)
1375
+ };
1376
+ }
1377
+ isUpdateAllowed(scope, credential, assertion) {
1378
+ if (assertion.role === import_credentials3.SpaceMember.Role.OWNER) {
1379
+ return credential.issuer.equals(this._spaceKey);
1380
+ }
1381
+ const issuer = credential.issuer;
1382
+ const isChangingOwnRole = issuer.equals(credential.subject.id);
1383
+ if (isChangingOwnRole) {
1384
+ return false;
1385
+ }
1386
+ if (issuer.equals(assertion.spaceKey)) {
1387
+ return true;
1388
+ }
1389
+ const issuerRole = this._getRole(scope, issuer);
1390
+ return issuerRole === import_credentials3.SpaceMember.Role.ADMIN || issuerRole === import_credentials3.SpaceMember.Role.OWNER;
1391
+ }
1392
+ getConflictingPaths(paths, update) {
1393
+ if (update.assertion.role !== import_credentials3.SpaceMember.Role.REMOVED && update.assertion.role !== import_credentials3.SpaceMember.Role.EDITOR) {
1394
+ return [];
1395
+ }
1396
+ const memberId = update.credential.subject.id;
1397
+ return paths.filter((p) => p.forkIssuers.has(memberId));
1398
+ }
1399
+ tryPickWinningUpdate(scope1, update1, scope2, update2) {
1400
+ const path1IssuerRole = this._getRole(scope1, update1.issuer);
1401
+ const path2IssuerRole = this._getRole(scope2, update2.issuer);
1402
+ if (path2IssuerRole === import_credentials3.SpaceMember.Role.OWNER !== (path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER)) {
1403
+ (0, import_log2.log)("owner decision used to break the tie", void 0, {
1404
+ F: __dxlog_file4,
1405
+ L: 139,
1406
+ S: this,
1407
+ C: (f, a) => f(...a)
1408
+ });
1409
+ return path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER ? update1 : update2;
1410
+ }
1411
+ return null;
1412
+ }
1413
+ toLogString(assertion) {
1414
+ const role = assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
1415
+ return Object.entries(import_credentials3.SpaceMember.Role).find(([_, value]) => value === role)[0];
1416
+ }
1417
+ hasStateChanged(s1, s2) {
1418
+ return s1?.role !== s2?.role;
1419
+ }
1420
+ _getRole(scope, memberId) {
1421
+ if (this._ownerKey?.equals(memberId)) {
1422
+ return import_credentials3.SpaceMember.Role.OWNER;
1423
+ }
1424
+ const realRole = scope.state.get(memberId)?.assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
1425
+ if (scope.stateOverrides != null) {
1426
+ const override = scope.stateOverrides.get(memberId);
1427
+ if (override != null) {
1428
+ (0, import_log2.log)("member role overridden in path", () => ({
1429
+ headId: scope.head?.id,
1430
+ roleOverride: this.toLogString(override.assertion),
1431
+ realRole: this.toLogString(scope.state.get(memberId)?.assertion)
1432
+ }), {
1433
+ F: __dxlog_file4,
1434
+ L: 162,
1435
+ S: this,
1436
+ C: (f, a) => f(...a)
1437
+ });
1438
+ return override.assertion.role;
818
1439
  }
819
1440
  }
1441
+ return realRole;
820
1442
  }
821
1443
  };
822
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
823
- var REVOCABLE_CREDENTIALS = [
824
- "dxos.halo.credentials.SpaceMember"
825
- ];
1444
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
826
1445
  var SpaceStateMachine = class {
827
1446
  constructor(_spaceKey) {
828
1447
  this._spaceKey = _spaceKey;
@@ -834,7 +1453,7 @@ var SpaceStateMachine = class {
834
1453
  this._processedCredentials = new import_util2.ComplexSet(import_keys3.PublicKey.hash);
835
1454
  this._credentialProcessors = [];
836
1455
  this.onCredentialProcessed = new import_util2.Callback();
837
- this.onMemberAdmitted = this._members.onMemberAdmitted;
1456
+ this.onMemberRoleChanged = this._members.onMemberRoleChanged;
838
1457
  this.onFeedAdmitted = this._feeds.onFeedAdmitted;
839
1458
  this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
840
1459
  this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
@@ -845,6 +1464,9 @@ var SpaceStateMachine = class {
845
1464
  get members() {
846
1465
  return this._members.members;
847
1466
  }
1467
+ get membershipChainHeads() {
1468
+ return this._members.membershipChainHeads;
1469
+ }
848
1470
  get feeds() {
849
1471
  return this._feeds.feeds;
850
1472
  }
@@ -897,8 +1519,8 @@ var SpaceStateMachine = class {
897
1519
  const result = await verifyCredential(credential);
898
1520
  if (result.kind !== "pass") {
899
1521
  import_log.log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
900
- F: __dxlog_file4,
901
- L: 149,
1522
+ F: __dxlog_file5,
1523
+ L: 152,
902
1524
  S: this,
903
1525
  C: (f, a) => f(...a)
904
1526
  });
@@ -907,43 +1529,11 @@ var SpaceStateMachine = class {
907
1529
  }
908
1530
  const assertion = getCredentialAssertion(credential);
909
1531
  switch (assertion["@type"]) {
910
- case "dxos.halo.credentials.Revocation": {
911
- if (!this._canRevokeCredentials(credential.issuer)) {
912
- import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
913
- F: __dxlog_file4,
914
- L: 158,
915
- S: this,
916
- C: (f, a) => f(...a)
917
- });
918
- return false;
919
- }
920
- const toBeRevoked = this._credentialsById.get(credential.subject.id);
921
- if (!toBeRevoked) {
922
- import_log.log.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
923
- F: __dxlog_file4,
924
- L: 163,
925
- S: this,
926
- C: (f, a) => f(...a)
927
- });
928
- return false;
929
- }
930
- if (!this._credentialCanBeRevoked(toBeRevoked)) {
931
- import_log.log.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
932
- F: __dxlog_file4,
933
- L: 167,
934
- S: this,
935
- C: (f, a) => f(...a)
936
- });
937
- }
938
- toBeRevoked.revoked = true;
939
- await this._members.onRevoked(toBeRevoked.credential, credential);
940
- break;
941
- }
942
1532
  case "dxos.halo.credentials.SpaceGenesis": {
943
1533
  if (this._genesisCredential) {
944
1534
  import_log.log.warn("Space already has a genesis credential.", void 0, {
945
- F: __dxlog_file4,
946
- L: 176,
1535
+ F: __dxlog_file5,
1536
+ L: 161,
947
1537
  S: this,
948
1538
  C: (f, a) => f(...a)
949
1539
  });
@@ -951,8 +1541,8 @@ var SpaceStateMachine = class {
951
1541
  }
952
1542
  if (!credential.issuer.equals(this._spaceKey)) {
953
1543
  import_log.log.warn("Space genesis credential must be issued by space.", void 0, {
954
- F: __dxlog_file4,
955
- L: 180,
1544
+ F: __dxlog_file5,
1545
+ L: 165,
956
1546
  S: this,
957
1547
  C: (f, a) => f(...a)
958
1548
  });
@@ -960,8 +1550,8 @@ var SpaceStateMachine = class {
960
1550
  }
961
1551
  if (!credential.subject.id.equals(this._spaceKey)) {
962
1552
  import_log.log.warn("Space genesis credential must be issued to space.", void 0, {
963
- F: __dxlog_file4,
964
- L: 184,
1553
+ F: __dxlog_file5,
1554
+ L: 169,
965
1555
  S: this,
966
1556
  C: (f, a) => f(...a)
967
1557
  });
@@ -976,8 +1566,8 @@ var SpaceStateMachine = class {
976
1566
  }
977
1567
  if (!this._genesisCredential) {
978
1568
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
979
- F: __dxlog_file4,
980
- L: 198,
1569
+ F: __dxlog_file5,
1570
+ L: 182,
981
1571
  S: this,
982
1572
  C: (f, a) => f(...a)
983
1573
  });
@@ -985,8 +1575,8 @@ var SpaceStateMachine = class {
985
1575
  }
986
1576
  if (!this._canInviteNewMembers(credential.issuer)) {
987
1577
  import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
988
- F: __dxlog_file4,
989
- L: 202,
1578
+ F: __dxlog_file5,
1579
+ L: 186,
990
1580
  S: this,
991
1581
  C: (f, a) => f(...a)
992
1582
  });
@@ -999,8 +1589,8 @@ var SpaceStateMachine = class {
999
1589
  case "dxos.halo.credentials.MemberProfile": {
1000
1590
  if (!this._genesisCredential) {
1001
1591
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
1002
- F: __dxlog_file4,
1003
- L: 213,
1592
+ F: __dxlog_file5,
1593
+ L: 197,
1004
1594
  S: this,
1005
1595
  C: (f, a) => f(...a)
1006
1596
  });
@@ -1012,8 +1602,8 @@ var SpaceStateMachine = class {
1012
1602
  case "dxos.halo.credentials.AdmittedFeed": {
1013
1603
  if (!this._genesisCredential) {
1014
1604
  import_log.log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
1015
- F: __dxlog_file4,
1016
- L: 223,
1605
+ F: __dxlog_file5,
1606
+ L: 207,
1017
1607
  S: this,
1018
1608
  C: (f, a) => f(...a)
1019
1609
  });
@@ -1021,8 +1611,8 @@ var SpaceStateMachine = class {
1021
1611
  }
1022
1612
  if (!this._canAdmitFeeds(credential.issuer)) {
1023
1613
  import_log.log.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
1024
- F: __dxlog_file4,
1025
- L: 227,
1614
+ F: __dxlog_file5,
1615
+ L: 211,
1026
1616
  S: this,
1027
1617
  C: (f, a) => f(...a)
1028
1618
  });
@@ -1035,8 +1625,8 @@ var SpaceStateMachine = class {
1035
1625
  case "dxos.halo.invitations.DelegateSpaceInvitation": {
1036
1626
  if (!this._canInviteNewMembers(credential.issuer)) {
1037
1627
  import_log.log.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1038
- F: __dxlog_file4,
1039
- L: 238,
1628
+ F: __dxlog_file5,
1629
+ L: 222,
1040
1630
  S: this,
1041
1631
  C: (f, a) => f(...a)
1042
1632
  });
@@ -1064,17 +1654,11 @@ var SpaceStateMachine = class {
1064
1654
  return true;
1065
1655
  }
1066
1656
  _canInviteNewMembers(key) {
1067
- return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
1657
+ return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN || this._members.getRole(key) === import_credentials2.SpaceMember.Role.OWNER;
1068
1658
  }
1069
1659
  _canAdmitFeeds(key) {
1070
1660
  const role = this._members.getRole(key);
1071
- return role === import_credentials2.SpaceMember.Role.MEMBER || role === import_credentials2.SpaceMember.Role.ADMIN;
1072
- }
1073
- _canRevokeCredentials(key) {
1074
- return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
1075
- }
1076
- _credentialCanBeRevoked(entry) {
1077
- return REVOCABLE_CREDENTIALS.includes(entry.credential.subject.assertion["@type"]);
1661
+ return role === import_credentials2.SpaceMember.Role.EDITOR || role === import_credentials2.SpaceMember.Role.ADMIN || role === import_credentials2.SpaceMember.Role.OWNER;
1078
1662
  }
1079
1663
  };
1080
1664
  var CredentialConsumer = class {
@@ -1104,20 +1688,20 @@ var CredentialConsumer = class {
1104
1688
  await this._onClose();
1105
1689
  }
1106
1690
  };
1107
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1691
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1108
1692
  var DeviceStateMachine = class {
1109
1693
  constructor(_params) {
1110
1694
  this._params = _params;
1111
- this.authorizedDeviceKeys = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1695
+ this.authorizedDeviceKeys = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
1112
1696
  this.deviceChainReady = new import_async2.Trigger();
1113
1697
  }
1114
1698
  async processCredential(credential) {
1115
- (0, import_log3.log)("processing credential...", {
1699
+ (0, import_log4.log)("processing credential...", {
1116
1700
  identityKey: this._params.identityKey,
1117
1701
  deviceKey: this._params.deviceKey,
1118
1702
  credential
1119
1703
  }, {
1120
- F: __dxlog_file5,
1704
+ F: __dxlog_file6,
1121
1705
  L: 35,
1122
1706
  S: this,
1123
1707
  C: (f, a) => f(...a)
@@ -1131,8 +1715,8 @@ var DeviceStateMachine = class {
1131
1715
  const assertion = getCredentialAssertion(credential);
1132
1716
  switch (assertion["@type"]) {
1133
1717
  case "dxos.halo.credentials.AuthorizedDevice": {
1134
- (0, import_invariant4.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1135
- F: __dxlog_file5,
1718
+ (0, import_invariant5.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1719
+ F: __dxlog_file6,
1136
1720
  L: 51,
1137
1721
  S: this,
1138
1722
  A: [
@@ -1141,12 +1725,12 @@ var DeviceStateMachine = class {
1141
1725
  ]
1142
1726
  });
1143
1727
  this.authorizedDeviceKeys.set(assertion.deviceKey, {});
1144
- (0, import_log3.log)("added device", {
1728
+ (0, import_log4.log)("added device", {
1145
1729
  localDeviceKey: this._params.deviceKey,
1146
1730
  deviceKey: assertion.deviceKey,
1147
1731
  size: this.authorizedDeviceKeys.size
1148
1732
  }, {
1149
- F: __dxlog_file5,
1733
+ F: __dxlog_file6,
1150
1734
  L: 54,
1151
1735
  S: this,
1152
1736
  C: (f, a) => f(...a)
@@ -1155,8 +1739,8 @@ var DeviceStateMachine = class {
1155
1739
  break;
1156
1740
  }
1157
1741
  case "dxos.halo.credentials.DeviceProfile": {
1158
- (0, import_invariant4.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1159
- F: __dxlog_file5,
1742
+ (0, import_invariant5.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1743
+ F: __dxlog_file6,
1160
1744
  L: 64,
1161
1745
  S: this,
1162
1746
  A: [
@@ -1165,11 +1749,11 @@ var DeviceStateMachine = class {
1165
1749
  ]
1166
1750
  });
1167
1751
  if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
1168
- import_log3.log.trace("dxos.halo.device", {
1752
+ import_log4.log.trace("dxos.halo.device", {
1169
1753
  deviceKey: credential.subject.id,
1170
1754
  profile: assertion.profile
1171
1755
  }, {
1172
- F: __dxlog_file5,
1756
+ F: __dxlog_file6,
1173
1757
  L: 67,
1174
1758
  S: this,
1175
1759
  C: (f, a) => f(...a)
@@ -1182,7 +1766,7 @@ var DeviceStateMachine = class {
1182
1766
  }
1183
1767
  }
1184
1768
  };
1185
- var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1769
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1186
1770
  var ProfileStateMachine = class {
1187
1771
  constructor(_params) {
1188
1772
  this._params = _params;
@@ -1192,11 +1776,11 @@ var ProfileStateMachine = class {
1192
1776
  switch (assertion["@type"]) {
1193
1777
  case "dxos.halo.credentials.IdentityProfile": {
1194
1778
  if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
1195
- import_log4.log.warn("Invalid profile credential", {
1779
+ import_log5.log.warn("Invalid profile credential", {
1196
1780
  expectedIdentity: this._params.identityKey,
1197
1781
  credential
1198
1782
  }, {
1199
- F: __dxlog_file6,
1783
+ F: __dxlog_file7,
1200
1784
  L: 34,
1201
1785
  S: this,
1202
1786
  C: (f, a) => f(...a)
@@ -1204,11 +1788,11 @@ var ProfileStateMachine = class {
1204
1788
  return;
1205
1789
  }
1206
1790
  this.profile = assertion.profile;
1207
- (0, import_log4.log)("updated profile", {
1791
+ (0, import_log5.log)("updated profile", {
1208
1792
  identityKey: this._params.identityKey,
1209
1793
  profile: this.profile
1210
1794
  }, {
1211
- F: __dxlog_file6,
1795
+ F: __dxlog_file7,
1212
1796
  L: 40,
1213
1797
  S: this,
1214
1798
  C: (f, a) => f(...a)
@@ -1232,6 +1816,7 @@ var ProfileStateMachine = class {
1232
1816
  canonicalStringify,
1233
1817
  checkCredentialType,
1234
1818
  createAdmissionCredentials,
1819
+ createCancelDelegatedSpaceInvitationCredential,
1235
1820
  createCredential,
1236
1821
  createCredentialMessage,
1237
1822
  createCredentialSignerWithChain,