@dxos/credentials 0.5.1-main.295efe4 → 0.5.1-main.2daa51f

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 (31) hide show
  1. package/dist/lib/browser/index.mjs +740 -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 +713 -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 +12 -2
  10. package/dist/types/src/credentials/credential-generator.d.ts.map +1 -1
  11. package/dist/types/src/credentials/verifier.d.ts +1 -1
  12. package/dist/types/src/credentials/verifier.d.ts.map +1 -1
  13. package/dist/types/src/graph/credential-graph.d.ts +152 -0
  14. package/dist/types/src/graph/credential-graph.d.ts.map +1 -0
  15. package/dist/types/src/state-machine/member-state-machine.d.ts +17 -12
  16. package/dist/types/src/state-machine/member-state-machine.d.ts.map +1 -1
  17. package/dist/types/src/state-machine/member-state-machine.test.d.ts +2 -0
  18. package/dist/types/src/state-machine/member-state-machine.test.d.ts.map +1 -0
  19. package/dist/types/src/state-machine/space-state-machine.d.ts +3 -3
  20. package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
  21. package/package.json +16 -16
  22. package/src/credentials/credential-factory.ts +9 -1
  23. package/src/credentials/credential-generator.ts +15 -2
  24. package/src/credentials/verifier.test.ts +25 -0
  25. package/src/credentials/verifier.ts +5 -1
  26. package/src/graph/credential-graph.ts +557 -0
  27. package/src/state-machine/invitation-state-machine.test.ts +1 -1
  28. package/src/state-machine/member-state-machine.test.ts +580 -0
  29. package/src/state-machine/member-state-machine.ts +111 -53
  30. package/src/state-machine/space-state-machine.test.ts +20 -16
  31. package/src/state-machine/space-state-machine.ts +12 -33
@@ -112,6 +112,9 @@ import { PublicKey as PublicKey2 } from "@dxos/keys";
112
112
  import { verifySignature } from "@dxos/crypto";
113
113
  var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
114
114
  var verifyCredential = async (credential) => {
115
+ if (credential.parentCredentialIds?.length === 0) {
116
+ delete credential.parentCredentialIds;
117
+ }
115
118
  if (!credential.issuer.equals(credential.proof.signer)) {
116
119
  if (!credential.proof.chain) {
117
120
  return {
@@ -176,10 +179,10 @@ var verifyChain = async (chain, authority, subject) => {
176
179
 
177
180
  // packages/core/halo/credentials/src/credentials/credential-factory.ts
178
181
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
179
- var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
182
+ var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
180
183
  invariant(assertion["@type"], "Invalid assertion.", {
181
184
  F: __dxlog_file,
182
- L: 45,
185
+ L: 48,
183
186
  S: void 0,
184
187
  A: [
185
188
  "assertion['@type']",
@@ -188,7 +191,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
188
191
  });
189
192
  invariant(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
190
193
  F: __dxlog_file,
191
- L: 46,
194
+ L: 49,
192
195
  S: void 0,
193
196
  A: [
194
197
  "!!signingKey === !!chain",
@@ -199,7 +202,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
199
202
  const result = await verifyChain(chain, issuer, signingKey);
200
203
  invariant(result.kind === "pass", "Invalid chain.", {
201
204
  F: __dxlog_file,
202
- L: 49,
205
+ L: 52,
203
206
  S: void 0,
204
207
  A: [
205
208
  "result.kind === 'pass'",
@@ -222,6 +225,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
222
225
  nonce
223
226
  }
224
227
  };
228
+ if ((parentCredentialIds?.length ?? 0) > 0) {
229
+ credential.parentCredentialIds = parentCredentialIds;
230
+ }
225
231
  const signedPayload = getCredentialProofPayload(credential);
226
232
  credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
227
233
  if (chain) {
@@ -238,12 +244,13 @@ var createCredentialMessage = (credential) => {
238
244
  };
239
245
  var createCredentialSignerWithKey = (signer, issuer) => ({
240
246
  getIssuer: () => issuer,
241
- createCredential: ({ subject, assertion, nonce }) => createCredential({
247
+ createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
242
248
  signer,
243
249
  issuer,
244
250
  subject,
245
251
  assertion,
246
- nonce
252
+ nonce,
253
+ parentCredentialIds
247
254
  })
248
255
  });
249
256
  var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
@@ -311,7 +318,7 @@ var CredentialGenerator = class {
311
318
  assertion: {
312
319
  "@type": "dxos.halo.credentials.SpaceMember",
313
320
  spaceKey,
314
- role: SpaceMember.Role.MEMBER,
321
+ role: SpaceMember.Role.EDITOR,
315
322
  genesisFeedKey
316
323
  }
317
324
  }),
@@ -406,14 +413,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
406
413
  credential
407
414
  }));
408
415
  };
409
- var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, profile, invitationCredentialId) => {
416
+ var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId) => {
410
417
  const credentials = await Promise.all([
411
418
  await signer.createCredential({
412
419
  subject: identityKey,
420
+ parentCredentialIds: membershipChainHeads,
413
421
  assertion: {
414
422
  "@type": "dxos.halo.credentials.SpaceMember",
415
423
  spaceKey,
416
- role: SpaceMember.Role.ADMIN,
424
+ role,
417
425
  profile,
418
426
  genesisFeedKey,
419
427
  invitationCredentialId
@@ -571,10 +579,10 @@ var verifyPresentationSignature = async (presentation, proof) => {
571
579
  // packages/core/halo/credentials/src/state-machine/space-state-machine.ts
572
580
  import { runInContextAsync } from "@dxos/async";
573
581
  import { Context } from "@dxos/context";
574
- import { PublicKey as PublicKey6 } from "@dxos/keys";
575
- import { log as log2 } from "@dxos/log";
576
- import { SpaceMember as SpaceMember2 } from "@dxos/protocols/proto/dxos/halo/credentials";
577
- import { Callback as Callback4, ComplexMap as ComplexMap4, ComplexSet as ComplexSet2 } from "@dxos/util";
582
+ import { PublicKey as PublicKey7 } from "@dxos/keys";
583
+ import { log as log3 } from "@dxos/log";
584
+ import { SpaceMember as SpaceMember3 } from "@dxos/protocols/proto/dxos/halo/credentials";
585
+ import { Callback as Callback4, ComplexMap as ComplexMap5, ComplexSet as ComplexSet3 } from "@dxos/util";
578
586
 
579
587
  // packages/core/halo/credentials/src/state-machine/feed-state-machine.ts
580
588
  import { invariant as invariant2 } from "@dxos/invariant";
@@ -698,25 +706,587 @@ var InvitationStateMachine = class {
698
706
  };
699
707
 
700
708
  // packages/core/halo/credentials/src/state-machine/member-state-machine.ts
709
+ import { invariant as invariant4 } from "@dxos/invariant";
710
+ import { PublicKey as PublicKey6 } from "@dxos/keys";
711
+ import { log as log2 } from "@dxos/log";
712
+ import { SpaceMember as SpaceMember2 } from "@dxos/protocols/proto/dxos/halo/credentials";
713
+ import { ComplexMap as ComplexMap4 } from "@dxos/util";
714
+
715
+ // packages/core/halo/credentials/src/graph/credential-graph.ts
701
716
  import { invariant as invariant3 } from "@dxos/invariant";
702
717
  import { PublicKey as PublicKey5 } from "@dxos/keys";
703
718
  import { log } from "@dxos/log";
704
- import { Callback as Callback3, ComplexMap as ComplexMap3 } from "@dxos/util";
705
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
719
+ import { Callback as Callback3, ComplexMap as ComplexMap3, ComplexSet as ComplexSet2 } from "@dxos/util";
720
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
721
+ var CredentialGraph = class {
722
+ constructor(_stateHandler) {
723
+ this._stateHandler = _stateHandler;
724
+ this._vertexIdGenerator = 1;
725
+ this._root = {
726
+ id: -1,
727
+ parents: [],
728
+ children: []
729
+ };
730
+ this._sentinel = {
731
+ id: -2,
732
+ parents: [],
733
+ children: []
734
+ };
735
+ this._vertexByCredentialId = new ComplexMap3(PublicKey5.hash);
736
+ this._subjectToVertex = new ComplexMap3(PublicKey5.hash);
737
+ this._subjectToState = new ComplexMap3(PublicKey5.hash);
738
+ this.onSubjectStateChanged = new Callback3();
739
+ }
740
+ getSubjectState(subjectId) {
741
+ return this._subjectToState.get(subjectId);
742
+ }
743
+ getState() {
744
+ return this._subjectToState;
745
+ }
746
+ getLeafIds() {
747
+ return this._sentinel.parents.map((v) => v.credential.id);
748
+ }
749
+ getGlobalStateScope() {
750
+ return {
751
+ state: this._subjectToVertex
752
+ };
753
+ }
754
+ addVertex(credential, assertion) {
755
+ const newVertex = {
756
+ id: this._vertexIdGenerator++,
757
+ credential,
758
+ assertion,
759
+ parents: [],
760
+ children: []
761
+ };
762
+ this._vertexByCredentialId.set(credential.id, newVertex);
763
+ const parentIds = credential.parentCredentialIds ?? [];
764
+ if (parentIds.length === 0) {
765
+ this._root.children.push(newVertex);
766
+ newVertex.parents.push(this._root);
767
+ } else {
768
+ for (const parentId of parentIds) {
769
+ const parentVertex = this._vertexByCredentialId.get(parentId);
770
+ if (parentVertex == null) {
771
+ log.error("credential skipped because of the unknown parent", {
772
+ credential,
773
+ parentId
774
+ }, {
775
+ F: __dxlog_file3,
776
+ L: 72,
777
+ S: this,
778
+ C: (f, a) => f(...a)
779
+ });
780
+ continue;
781
+ }
782
+ parentVertex.children.push(newVertex);
783
+ newVertex.parents.push(parentVertex);
784
+ this._removeSentinelConnection(parentVertex);
785
+ }
786
+ }
787
+ newVertex.children.push(this._sentinel);
788
+ this._sentinel.parents.push(newVertex);
789
+ return this._onVertexInserted(newVertex);
790
+ }
791
+ _removeSentinelConnection(vertex) {
792
+ const sentinelIdx = vertex.children.indexOf(this._sentinel);
793
+ if (sentinelIdx >= 0) {
794
+ vertex.children.splice(sentinelIdx, 1);
795
+ const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
796
+ invariant3(vertexInSentinelIdx >= 0, void 0, {
797
+ F: __dxlog_file3,
798
+ L: 90,
799
+ S: this,
800
+ A: [
801
+ "vertexInSentinelIdx >= 0",
802
+ ""
803
+ ]
804
+ });
805
+ this._sentinel.parents.splice(vertexInSentinelIdx, 1);
806
+ }
807
+ }
808
+ async _onVertexInserted(newVertex) {
809
+ const { credential, assertion } = newVertex;
810
+ invariant3(credential, void 0, {
811
+ F: __dxlog_file3,
812
+ L: 97,
813
+ S: this,
814
+ A: [
815
+ "credential",
816
+ ""
817
+ ]
818
+ });
819
+ let changedSubjects = [];
820
+ const isUpdateAppliedOnTopOfThePreviousState = this._sentinel.parents.length === 1;
821
+ if (isUpdateAppliedOnTopOfThePreviousState) {
822
+ const subjectId = credential.subject.id;
823
+ if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
824
+ const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
825
+ const prevSubjectState = this._subjectToState.get(subjectId);
826
+ this._subjectToState.set(subjectId, newSubjectState);
827
+ this._subjectToVertex.set(subjectId, newVertex);
828
+ if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) {
829
+ changedSubjects.push(newSubjectState);
830
+ }
831
+ }
832
+ } else {
833
+ changedSubjects = this._recomputeState();
834
+ }
835
+ if (changedSubjects.length > 0) {
836
+ await this.onSubjectStateChanged.callIfSet(changedSubjects);
837
+ }
838
+ }
839
+ /**
840
+ * DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
841
+ * Continue after all paths leading to a merge point converge by merging their states.
842
+ * In case of a concurrent update paths are replayed taking into account the state set
843
+ * by the winning branch.
844
+ */
845
+ _recomputeState() {
846
+ const pendingPaths = /* @__PURE__ */ new Map();
847
+ const paths = [
848
+ this._createRootPath()
849
+ ];
850
+ let lastPath = null;
851
+ while (lastPath == null) {
852
+ const path = paths.pop();
853
+ log("visit vertex", {
854
+ id: path.head.id
855
+ }, {
856
+ F: __dxlog_file3,
857
+ L: 132,
858
+ S: this,
859
+ C: (f, a) => f(...a)
860
+ });
861
+ this._updatePathState(path);
862
+ const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
863
+ if (convergedPaths == null) {
864
+ log("waiting for other paths", void 0, {
865
+ F: __dxlog_file3,
866
+ L: 136,
867
+ S: this,
868
+ C: (f, a) => f(...a)
869
+ });
870
+ continue;
871
+ }
872
+ const mergeResult = this._mergePaths(convergedPaths);
873
+ if (mergeResult.type === "replay_required") {
874
+ this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
875
+ continue;
876
+ }
877
+ const merged = mergeResult.path;
878
+ if (merged.head.children.length === 0) {
879
+ lastPath = merged;
880
+ } else if (merged.head.children.length === 1) {
881
+ merged.head = merged.head.children[0];
882
+ paths.push(merged);
883
+ } else {
884
+ this._forkTraversal(paths, merged);
885
+ }
886
+ }
887
+ if (paths.length > 0) {
888
+ log.error("traversal finished while there were active paths", {
889
+ paths: paths.map((p) => ({
890
+ path: toChosenPath(p),
891
+ head: p.head.id
892
+ }))
893
+ }, {
894
+ F: __dxlog_file3,
895
+ L: 155,
896
+ S: this,
897
+ C: (f, a) => f(...a)
898
+ });
899
+ }
900
+ return this._setCurrentState(lastPath);
901
+ }
902
+ _replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
903
+ paths.push(...mergeResult.replay.map((path) => {
904
+ const stateOverrides = path.stateOverrides ?? new ComplexMap3(PublicKey5.hash);
905
+ mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
906
+ return {
907
+ ...mergeResult.from,
908
+ chosenPath: path.chosenPath,
909
+ stateOverrides
910
+ };
911
+ }));
912
+ log("replay paths", () => ({
913
+ count: paths.length,
914
+ paths: paths.map((path) => ({
915
+ from: mergeResult.from.head.id,
916
+ path: toChosenPath(path),
917
+ overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
918
+ }))
919
+ }), {
920
+ F: __dxlog_file3,
921
+ L: 175,
922
+ S: this,
923
+ C: (f, a) => f(...a)
924
+ });
925
+ const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
926
+ pendingPaths.set(convergedPaths[0].head.id, clearedPending);
927
+ }
928
+ _handleMergePoint(paths, pendingPaths, path) {
929
+ const pendingList = pendingPaths.get(path.head.id) ?? [];
930
+ pendingPaths.set(path.head.id, pendingList);
931
+ pendingList.push(path);
932
+ if (pendingList.length < path.head.parents.length) {
933
+ return null;
934
+ }
935
+ if (path.head.id === this._sentinel.id && paths.length > 0) {
936
+ log("waiting for all the active paths to converge on the sentinel", void 0, {
937
+ F: __dxlog_file3,
938
+ L: 199,
939
+ S: this,
940
+ C: (f, a) => f(...a)
941
+ });
942
+ return null;
943
+ }
944
+ pendingPaths.delete(path.head.id);
945
+ return pendingList;
946
+ }
947
+ _updatePathState(path) {
948
+ const headCredential = path.head.credential;
949
+ if (headCredential == null) {
950
+ return;
951
+ }
952
+ const updatedSubject = headCredential.subject.id;
953
+ path.credentials.add(headCredential.id);
954
+ let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
955
+ if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
956
+ const globalState = this.getGlobalStateScope();
957
+ isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
958
+ }
959
+ if (isUpdateAllowed) {
960
+ path.forkChangedSubjects.add(updatedSubject);
961
+ path.forkIssuers.add(headCredential.issuer);
962
+ path.state.set(updatedSubject, path.head);
963
+ log("path state updated", () => ({
964
+ subject: updatedSubject,
965
+ newState: this._stateHandler.toLogString(path.head.assertion)
966
+ }), {
967
+ F: __dxlog_file3,
968
+ L: 223,
969
+ S: this,
970
+ C: (f, a) => f(...a)
971
+ });
972
+ }
973
+ }
974
+ _forkTraversal(paths, path) {
975
+ const replayChoice = path.chosenPath?.[path.head.id];
976
+ const choices = replayChoice ?? path.head.children;
977
+ for (const choice of choices) {
978
+ log("edge traversal", {
979
+ from: path.head.id,
980
+ to: choice.id
981
+ }, {
982
+ F: __dxlog_file3,
983
+ L: 234,
984
+ S: this,
985
+ C: (f, a) => f(...a)
986
+ });
987
+ const fork = {
988
+ forkPoint: path,
989
+ chosenPath: {
990
+ ...path.chosenPath,
991
+ [path.head.id]: [
992
+ choice
993
+ ]
994
+ },
995
+ head: choice,
996
+ credentials: new ComplexSet2(PublicKey5.hash, path.credentials),
997
+ state: new ComplexMap3(PublicKey5.hash, [
998
+ ...path.state.entries()
999
+ ]),
1000
+ forkIssuers: new ComplexSet2(PublicKey5.hash),
1001
+ forkChangedSubjects: new ComplexSet2(PublicKey5.hash),
1002
+ stateOverrides: path.stateOverrides
1003
+ };
1004
+ paths.push(fork);
1005
+ }
1006
+ }
1007
+ /**
1008
+ * Updates the current graph state.
1009
+ * @returns changed states.
1010
+ */
1011
+ _setCurrentState(path) {
1012
+ const changedSubjects = [];
1013
+ const newStateMap = new ComplexMap3(PublicKey5.hash);
1014
+ const newVertexMap = new ComplexMap3(PublicKey5.hash);
1015
+ for (const [subjectKey, subjectVertex] of path.state.entries()) {
1016
+ const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
1017
+ const prevState = this._subjectToState.get(subjectKey);
1018
+ newStateMap.set(subjectKey, newState);
1019
+ newVertexMap.set(subjectKey, subjectVertex);
1020
+ if (this._stateHandler.hasStateChanged(newState, prevState)) {
1021
+ changedSubjects.push(newState);
1022
+ }
1023
+ }
1024
+ this._subjectToState = newStateMap;
1025
+ this._subjectToVertex = newVertexMap;
1026
+ return changedSubjects;
1027
+ }
1028
+ /*
1029
+ * Walk up all the fork points and return the first one present in all the paths.
1030
+ * We use local id to determine vertex position in the graph, because nodes can't
1031
+ * be inserted in the middle (between a parent and a child) and ids are monotonically increasing.
1032
+ */
1033
+ _leastCommonAncestor(paths) {
1034
+ const uniqueForkPoints = paths.reduce((acc, path) => {
1035
+ let it = path.forkPoint;
1036
+ while (it) {
1037
+ acc.set(it.head.id, it);
1038
+ it = it.forkPoint;
1039
+ }
1040
+ return acc;
1041
+ }, /* @__PURE__ */ new Map());
1042
+ let maxId = this._root.id;
1043
+ let maxState = null;
1044
+ for (const [id, state] of uniqueForkPoints.entries()) {
1045
+ const headCredential = state.head.credential;
1046
+ if (headCredential != null) {
1047
+ const isPointInEveryPath = paths.every((p) => p.credentials.has(headCredential.id));
1048
+ if (isPointInEveryPath && id > maxId) {
1049
+ maxId = id;
1050
+ maxState = state;
1051
+ }
1052
+ }
1053
+ }
1054
+ return maxState ?? this._createRootPath();
1055
+ }
1056
+ /**
1057
+ * We might be merging paths where some of them had fork points after the initial forking.
1058
+ * We need all the paths to point to the least common fork point and contain all the changes
1059
+ * that happened after it.
1060
+ */
1061
+ _moveUpToForkPoint(forkPoint, path) {
1062
+ const isForkPointInPath = path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null;
1063
+ if (isForkPointInPath) {
1064
+ return path;
1065
+ }
1066
+ if (forkPoint.head.id === path.forkPoint?.head.id) {
1067
+ return path;
1068
+ }
1069
+ let it = path.forkPoint;
1070
+ while (it.head.id !== forkPoint.head.id) {
1071
+ it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
1072
+ it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
1073
+ it = it.forkPoint;
1074
+ path.forkPoint = it;
1075
+ }
1076
+ return path;
1077
+ }
1078
+ _mergePaths(convergedPaths) {
1079
+ invariant3(convergedPaths.length >= 1, void 0, {
1080
+ F: __dxlog_file3,
1081
+ L: 324,
1082
+ S: this,
1083
+ A: [
1084
+ "convergedPaths.length >= 1",
1085
+ ""
1086
+ ]
1087
+ });
1088
+ if (convergedPaths.length === 1) {
1089
+ return {
1090
+ type: "merged",
1091
+ path: convergedPaths[0]
1092
+ };
1093
+ }
1094
+ const forkPoint = this._leastCommonAncestor(convergedPaths);
1095
+ log("merging paths", () => ({
1096
+ forkPointId: forkPoint.head.id,
1097
+ pathCount: convergedPaths.length,
1098
+ forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
1099
+ }), {
1100
+ F: __dxlog_file3,
1101
+ L: 329,
1102
+ S: this,
1103
+ C: (f, a) => f(...a)
1104
+ });
1105
+ const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
1106
+ invariant3(forkPoint, void 0, {
1107
+ F: __dxlog_file3,
1108
+ L: 335,
1109
+ S: this,
1110
+ A: [
1111
+ "forkPoint",
1112
+ ""
1113
+ ]
1114
+ });
1115
+ const result = {
1116
+ forkPoint: forkPoint.forkPoint,
1117
+ chosenPath: {
1118
+ ...forkPoint.chosenPath,
1119
+ [forkPoint.head.id]: []
1120
+ },
1121
+ stateOverrides: forkPoint.stateOverrides,
1122
+ credentials: new ComplexSet2(PublicKey5.hash, forkPoint.credentials),
1123
+ forkIssuers: new ComplexSet2(PublicKey5.hash, forkPoint.forkIssuers),
1124
+ forkChangedSubjects: new ComplexSet2(PublicKey5.hash, forkPoint.forkChangedSubjects),
1125
+ state: forkPoint.state.mapValues((v) => v),
1126
+ head: paths[0].head
1127
+ };
1128
+ const subjectToBranch = new ComplexMap3(PublicKey5.hash);
1129
+ for (const path of paths) {
1130
+ log("processing a path", () => ({
1131
+ choices: toChosenPath(path),
1132
+ modified: path.forkChangedSubjects,
1133
+ forkIssuers: path.forkIssuers,
1134
+ state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
1135
+ }), {
1136
+ F: __dxlog_file3,
1137
+ L: 348,
1138
+ S: this,
1139
+ C: (f, a) => f(...a)
1140
+ });
1141
+ path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
1142
+ path.credentials.forEach((cred) => result.credentials.add(cred));
1143
+ result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
1144
+ for (const modifiedSubject of path.forkChangedSubjects) {
1145
+ const existingBranch = subjectToBranch.get(modifiedSubject);
1146
+ if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) {
1147
+ subjectToBranch.set(modifiedSubject, path);
1148
+ }
1149
+ }
1150
+ }
1151
+ const replayPaths = /* @__PURE__ */ new Set();
1152
+ const addReplayPath = replayPaths.add.bind(replayPaths);
1153
+ for (const [subject, branch] of subjectToBranch.entries()) {
1154
+ result.forkChangedSubjects.add(subject);
1155
+ const vertex = branch.state.get(subject);
1156
+ result.state.set(subject, vertex);
1157
+ log("set subject state", () => ({
1158
+ subject,
1159
+ state: this._stateHandler.toLogString(vertex.assertion)
1160
+ }), {
1161
+ F: __dxlog_file3,
1162
+ L: 370,
1163
+ S: this,
1164
+ C: (f, a) => f(...a)
1165
+ });
1166
+ const otherPaths = paths.filter((p) => p !== branch);
1167
+ this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
1168
+ }
1169
+ if (replayPaths.size > 0) {
1170
+ return {
1171
+ type: "replay_required",
1172
+ replay: [
1173
+ ...replayPaths.values()
1174
+ ],
1175
+ from: forkPoint,
1176
+ stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
1177
+ };
1178
+ }
1179
+ return {
1180
+ type: "merged",
1181
+ path: result
1182
+ };
1183
+ }
1184
+ /**
1185
+ * A candidate credential is preferred over the existing credential if:
1186
+ * 1. It is the merge-point, because it's the last credential that was issued in awareness of all
1187
+ * the previously existing ones.
1188
+ * 2. A path where candidate was set contains existing credential in it, which means that the candidate
1189
+ * was issued after the existing credential by a legitimate issuer.
1190
+ * 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
1191
+ * 4. The path where candidate was set has more issuers than the existing path (longer branch).
1192
+ * 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
1193
+ */
1194
+ _shouldOverrideCredential(existing, candidate, modifiedSubject) {
1195
+ const candidateVertex = candidate.state.get(modifiedSubject);
1196
+ const currentVertex = existing.state.get(modifiedSubject);
1197
+ if (candidateVertex.id === currentVertex.id) {
1198
+ return false;
1199
+ }
1200
+ const mergePointId = existing.head.id;
1201
+ if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
1202
+ log("merge point chosen to break the tie", {
1203
+ mergePointId: existing.head.id
1204
+ }, {
1205
+ F: __dxlog_file3,
1206
+ L: 408,
1207
+ S: this,
1208
+ C: (f, a) => f(...a)
1209
+ });
1210
+ return mergePointId === candidateVertex.id;
1211
+ }
1212
+ const candidateCredential = candidateVertex.credential;
1213
+ const currentCredential = currentVertex.credential;
1214
+ if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
1215
+ log("one of the credentials was overridden in another branch", {
1216
+ current: currentVertex.id,
1217
+ candidate: candidateVertex.id
1218
+ }, {
1219
+ F: __dxlog_file3,
1220
+ L: 415,
1221
+ S: this,
1222
+ C: (f, a) => f(...a)
1223
+ });
1224
+ return candidate.credentials.has(currentCredential.id);
1225
+ }
1226
+ const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
1227
+ if (winningCredential != null) {
1228
+ return winningCredential === candidateCredential;
1229
+ }
1230
+ if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
1231
+ log("longer issuers branch used to break the tie", {
1232
+ issuerCount: [
1233
+ existing.forkIssuers.size,
1234
+ candidate.forkIssuers.size
1235
+ ]
1236
+ }, {
1237
+ F: __dxlog_file3,
1238
+ L: 432,
1239
+ S: this,
1240
+ C: (f, a) => f(...a)
1241
+ });
1242
+ return candidate.forkIssuers.size > existing.forkIssuers.size;
1243
+ }
1244
+ log("issuance date used to break the tie", void 0, {
1245
+ F: __dxlog_file3,
1246
+ L: 437,
1247
+ S: this,
1248
+ C: (f, a) => f(...a)
1249
+ });
1250
+ return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
1251
+ }
1252
+ _createRootPath() {
1253
+ return {
1254
+ head: this._root,
1255
+ chosenPath: {},
1256
+ forkIssuers: new ComplexSet2(PublicKey5.hash),
1257
+ forkChangedSubjects: new ComplexSet2(PublicKey5.hash),
1258
+ state: new ComplexMap3(PublicKey5.hash),
1259
+ credentials: new ComplexSet2(PublicKey5.hash)
1260
+ };
1261
+ }
1262
+ };
1263
+ var toChosenPath = (path) => {
1264
+ return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [
1265
+ k,
1266
+ vs.map((v) => v.id)
1267
+ ]));
1268
+ };
1269
+
1270
+ // packages/core/halo/credentials/src/state-machine/member-state-machine.ts
1271
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
706
1272
  var MemberStateMachine = class {
707
1273
  constructor(_spaceKey) {
708
1274
  this._spaceKey = _spaceKey;
709
- this._members = new ComplexMap3(PublicKey5.hash);
710
- this.onMemberAdmitted = new Callback3();
1275
+ this._memberProfiles = new ComplexMap4(PublicKey6.hash);
1276
+ this._hashgraph = new CredentialGraph(this);
1277
+ this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
711
1278
  }
712
1279
  get creator() {
713
- return this._creator;
1280
+ return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
714
1281
  }
715
1282
  get members() {
716
- return this._members;
1283
+ return this._hashgraph.getState();
1284
+ }
1285
+ get membershipChainHeads() {
1286
+ return this._hashgraph.getLeafIds();
717
1287
  }
718
1288
  getRole(member) {
719
- return this._members.get(member)?.assertion.role;
1289
+ return this._getRole(this._hashgraph.getGlobalStateScope(), member);
720
1290
  }
721
1291
  /**
722
1292
  * Processes the SpaceMember credential.
@@ -726,87 +1296,125 @@ var MemberStateMachine = class {
726
1296
  const assertion = getCredentialAssertion(credential);
727
1297
  switch (assertion["@type"]) {
728
1298
  case "dxos.halo.credentials.SpaceMember": {
729
- invariant3(assertion.spaceKey.equals(this._spaceKey), void 0, {
730
- F: __dxlog_file3,
731
- L: 58,
1299
+ invariant4(assertion.spaceKey.equals(this._spaceKey), void 0, {
1300
+ F: __dxlog_file4,
1301
+ L: 66,
732
1302
  S: this,
733
1303
  A: [
734
1304
  "assertion.spaceKey.equals(this._spaceKey)",
735
1305
  ""
736
1306
  ]
737
1307
  });
738
- if (this._members.has(credential.subject.id)) {
739
- return;
1308
+ if (this._ownerKey == null && credential.issuer === this._spaceKey) {
1309
+ this._ownerKey = credential.subject.id;
740
1310
  }
741
- const info = {
742
- key: credential.subject.id,
743
- credential,
744
- assertion,
745
- removed: false,
746
- profile: assertion.profile
747
- };
748
- if (!this._creator) {
749
- this._creator = info;
1311
+ if (assertion.profile != null) {
1312
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
750
1313
  }
751
- this._members.set(credential.subject.id, info);
752
- log("member added", {
753
- member: credential.subject.id,
754
- space: this._spaceKey,
755
- role: assertion.role,
756
- profile: assertion.profile
757
- }, {
758
- F: __dxlog_file3,
759
- L: 79,
760
- S: this,
761
- C: (f, a) => f(...a)
762
- });
763
- await this.onMemberAdmitted.callIfSet(info);
1314
+ await this._hashgraph.addVertex(credential, assertion);
764
1315
  break;
765
1316
  }
766
1317
  case "dxos.halo.credentials.MemberProfile": {
767
- const member = this._members.get(credential.subject.id);
768
- if (!member) {
769
- log.warn("Member not found", {
1318
+ const member = this._hashgraph.getSubjectState(credential.subject.id);
1319
+ if (member) {
1320
+ member.profile = assertion.profile;
1321
+ } else {
1322
+ log2.warn("Member not found", {
770
1323
  id: credential.subject.id
771
1324
  }, {
772
- F: __dxlog_file3,
773
- L: 93,
1325
+ F: __dxlog_file4,
1326
+ L: 81,
774
1327
  S: this,
775
1328
  C: (f, a) => f(...a)
776
1329
  });
777
- } else {
778
- member.profile = assertion.profile;
779
1330
  }
1331
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
780
1332
  break;
781
1333
  }
782
1334
  default:
783
1335
  throw new Error("Invalid assertion type");
784
1336
  }
785
1337
  }
786
- async onRevoked(revoked, revocation) {
787
- invariant3(revoked.id, void 0, {
788
- F: __dxlog_file3,
789
- L: 106,
790
- S: this,
791
- A: [
792
- "revoked.id",
793
- ""
794
- ]
795
- });
796
- for (const member of this._members.values()) {
797
- if (member.credential.id?.equals(revoked.id)) {
798
- member.removed = true;
799
- break;
1338
+ createState(credential, assertion) {
1339
+ const memberKey = credential.subject.id;
1340
+ return {
1341
+ key: memberKey,
1342
+ role: assertion.role,
1343
+ credential,
1344
+ assertion,
1345
+ profile: this._memberProfiles.get(memberKey)
1346
+ };
1347
+ }
1348
+ isUpdateAllowed(scope, credential, assertion) {
1349
+ if (assertion.role === SpaceMember2.Role.OWNER) {
1350
+ return credential.issuer.equals(this._spaceKey);
1351
+ }
1352
+ const issuer = credential.issuer;
1353
+ const isChangingOwnRole = issuer.equals(credential.subject.id);
1354
+ if (isChangingOwnRole) {
1355
+ return false;
1356
+ }
1357
+ if (issuer.equals(assertion.spaceKey)) {
1358
+ return true;
1359
+ }
1360
+ const issuerRole = this._getRole(scope, issuer);
1361
+ return issuerRole === SpaceMember2.Role.ADMIN || issuerRole === SpaceMember2.Role.OWNER;
1362
+ }
1363
+ getConflictingPaths(paths, update) {
1364
+ if (update.assertion.role !== SpaceMember2.Role.REMOVED && update.assertion.role !== SpaceMember2.Role.EDITOR) {
1365
+ return [];
1366
+ }
1367
+ const memberId = update.credential.subject.id;
1368
+ return paths.filter((p) => p.forkIssuers.has(memberId));
1369
+ }
1370
+ tryPickWinningUpdate(scope1, update1, scope2, update2) {
1371
+ const path1IssuerRole = this._getRole(scope1, update1.issuer);
1372
+ const path2IssuerRole = this._getRole(scope2, update2.issuer);
1373
+ if (path2IssuerRole === SpaceMember2.Role.OWNER !== (path1IssuerRole === SpaceMember2.Role.OWNER)) {
1374
+ log2("owner decision used to break the tie", void 0, {
1375
+ F: __dxlog_file4,
1376
+ L: 139,
1377
+ S: this,
1378
+ C: (f, a) => f(...a)
1379
+ });
1380
+ return path1IssuerRole === SpaceMember2.Role.OWNER ? update1 : update2;
1381
+ }
1382
+ return null;
1383
+ }
1384
+ toLogString(assertion) {
1385
+ const role = assertion?.role ?? SpaceMember2.Role.REMOVED;
1386
+ return Object.entries(SpaceMember2.Role).find(([_, value]) => value === role)[0];
1387
+ }
1388
+ hasStateChanged(s1, s2) {
1389
+ return s1?.role !== s2?.role;
1390
+ }
1391
+ _getRole(scope, memberId) {
1392
+ if (this._ownerKey?.equals(memberId)) {
1393
+ return SpaceMember2.Role.OWNER;
1394
+ }
1395
+ const realRole = scope.state.get(memberId)?.assertion?.role ?? SpaceMember2.Role.REMOVED;
1396
+ if (scope.stateOverrides != null) {
1397
+ const override = scope.stateOverrides.get(memberId);
1398
+ if (override != null) {
1399
+ log2("member role overridden in path", () => ({
1400
+ headId: scope.head?.id,
1401
+ roleOverride: this.toLogString(override.assertion),
1402
+ realRole: this.toLogString(scope.state.get(memberId)?.assertion)
1403
+ }), {
1404
+ F: __dxlog_file4,
1405
+ L: 162,
1406
+ S: this,
1407
+ C: (f, a) => f(...a)
1408
+ });
1409
+ return override.assertion.role;
800
1410
  }
801
1411
  }
1412
+ return realRole;
802
1413
  }
803
1414
  };
804
1415
 
805
1416
  // packages/core/halo/credentials/src/state-machine/space-state-machine.ts
806
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
807
- var REVOCABLE_CREDENTIALS = [
808
- "dxos.halo.credentials.SpaceMember"
809
- ];
1417
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
810
1418
  var SpaceStateMachine = class {
811
1419
  constructor(_spaceKey) {
812
1420
  this._spaceKey = _spaceKey;
@@ -814,11 +1422,11 @@ var SpaceStateMachine = class {
814
1422
  this._feeds = new FeedStateMachine(this._spaceKey);
815
1423
  this._invitations = new InvitationStateMachine();
816
1424
  this._credentials = [];
817
- this._credentialsById = new ComplexMap4(PublicKey6.hash);
818
- this._processedCredentials = new ComplexSet2(PublicKey6.hash);
1425
+ this._credentialsById = new ComplexMap5(PublicKey7.hash);
1426
+ this._processedCredentials = new ComplexSet3(PublicKey7.hash);
819
1427
  this._credentialProcessors = [];
820
1428
  this.onCredentialProcessed = new Callback4();
821
- this.onMemberAdmitted = this._members.onMemberAdmitted;
1429
+ this.onMemberRoleChanged = this._members.onMemberRoleChanged;
822
1430
  this.onFeedAdmitted = this._feeds.onFeedAdmitted;
823
1431
  this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
824
1432
  this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
@@ -829,6 +1437,9 @@ var SpaceStateMachine = class {
829
1437
  get members() {
830
1438
  return this._members.members;
831
1439
  }
1440
+ get membershipChainHeads() {
1441
+ return this._members.membershipChainHeads;
1442
+ }
832
1443
  get feeds() {
833
1444
  return this._feeds.feeds;
834
1445
  }
@@ -880,9 +1491,9 @@ var SpaceStateMachine = class {
880
1491
  if (!skipVerification) {
881
1492
  const result = await verifyCredential(credential);
882
1493
  if (result.kind !== "pass") {
883
- log2.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
884
- F: __dxlog_file4,
885
- L: 149,
1494
+ log3.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
1495
+ F: __dxlog_file5,
1496
+ L: 152,
886
1497
  S: this,
887
1498
  C: (f, a) => f(...a)
888
1499
  });
@@ -891,61 +1502,29 @@ var SpaceStateMachine = class {
891
1502
  }
892
1503
  const assertion = getCredentialAssertion(credential);
893
1504
  switch (assertion["@type"]) {
894
- case "dxos.halo.credentials.Revocation": {
895
- if (!this._canRevokeCredentials(credential.issuer)) {
896
- log2.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
897
- F: __dxlog_file4,
898
- L: 158,
899
- S: this,
900
- C: (f, a) => f(...a)
901
- });
902
- return false;
903
- }
904
- const toBeRevoked = this._credentialsById.get(credential.subject.id);
905
- if (!toBeRevoked) {
906
- log2.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
907
- F: __dxlog_file4,
908
- L: 163,
909
- S: this,
910
- C: (f, a) => f(...a)
911
- });
912
- return false;
913
- }
914
- if (!this._credentialCanBeRevoked(toBeRevoked)) {
915
- log2.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
916
- F: __dxlog_file4,
917
- L: 167,
918
- S: this,
919
- C: (f, a) => f(...a)
920
- });
921
- }
922
- toBeRevoked.revoked = true;
923
- await this._members.onRevoked(toBeRevoked.credential, credential);
924
- break;
925
- }
926
1505
  case "dxos.halo.credentials.SpaceGenesis": {
927
1506
  if (this._genesisCredential) {
928
- log2.warn("Space already has a genesis credential.", void 0, {
929
- F: __dxlog_file4,
930
- L: 176,
1507
+ log3.warn("Space already has a genesis credential.", void 0, {
1508
+ F: __dxlog_file5,
1509
+ L: 161,
931
1510
  S: this,
932
1511
  C: (f, a) => f(...a)
933
1512
  });
934
1513
  return false;
935
1514
  }
936
1515
  if (!credential.issuer.equals(this._spaceKey)) {
937
- log2.warn("Space genesis credential must be issued by space.", void 0, {
938
- F: __dxlog_file4,
939
- L: 180,
1516
+ log3.warn("Space genesis credential must be issued by space.", void 0, {
1517
+ F: __dxlog_file5,
1518
+ L: 165,
940
1519
  S: this,
941
1520
  C: (f, a) => f(...a)
942
1521
  });
943
1522
  return false;
944
1523
  }
945
1524
  if (!credential.subject.id.equals(this._spaceKey)) {
946
- log2.warn("Space genesis credential must be issued to space.", void 0, {
947
- F: __dxlog_file4,
948
- L: 184,
1525
+ log3.warn("Space genesis credential must be issued to space.", void 0, {
1526
+ F: __dxlog_file5,
1527
+ L: 169,
949
1528
  S: this,
950
1529
  C: (f, a) => f(...a)
951
1530
  });
@@ -959,18 +1538,18 @@ var SpaceStateMachine = class {
959
1538
  break;
960
1539
  }
961
1540
  if (!this._genesisCredential) {
962
- log2.warn("Space must have a genesis credential before adding members.", void 0, {
963
- F: __dxlog_file4,
964
- L: 198,
1541
+ log3.warn("Space must have a genesis credential before adding members.", void 0, {
1542
+ F: __dxlog_file5,
1543
+ L: 182,
965
1544
  S: this,
966
1545
  C: (f, a) => f(...a)
967
1546
  });
968
1547
  return false;
969
1548
  }
970
1549
  if (!this._canInviteNewMembers(credential.issuer)) {
971
- log2.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
972
- F: __dxlog_file4,
973
- L: 202,
1550
+ log3.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1551
+ F: __dxlog_file5,
1552
+ L: 186,
974
1553
  S: this,
975
1554
  C: (f, a) => f(...a)
976
1555
  });
@@ -982,9 +1561,9 @@ var SpaceStateMachine = class {
982
1561
  }
983
1562
  case "dxos.halo.credentials.MemberProfile": {
984
1563
  if (!this._genesisCredential) {
985
- log2.warn("Space must have a genesis credential before adding members.", void 0, {
986
- F: __dxlog_file4,
987
- L: 213,
1564
+ log3.warn("Space must have a genesis credential before adding members.", void 0, {
1565
+ F: __dxlog_file5,
1566
+ L: 197,
988
1567
  S: this,
989
1568
  C: (f, a) => f(...a)
990
1569
  });
@@ -995,18 +1574,18 @@ var SpaceStateMachine = class {
995
1574
  }
996
1575
  case "dxos.halo.credentials.AdmittedFeed": {
997
1576
  if (!this._genesisCredential) {
998
- log2.warn("Space must have a genesis credential before admitting feeds.", void 0, {
999
- F: __dxlog_file4,
1000
- L: 223,
1577
+ log3.warn("Space must have a genesis credential before admitting feeds.", void 0, {
1578
+ F: __dxlog_file5,
1579
+ L: 207,
1001
1580
  S: this,
1002
1581
  C: (f, a) => f(...a)
1003
1582
  });
1004
1583
  return false;
1005
1584
  }
1006
1585
  if (!this._canAdmitFeeds(credential.issuer)) {
1007
- log2.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
1008
- F: __dxlog_file4,
1009
- L: 227,
1586
+ log3.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
1587
+ F: __dxlog_file5,
1588
+ L: 211,
1010
1589
  S: this,
1011
1590
  C: (f, a) => f(...a)
1012
1591
  });
@@ -1018,9 +1597,9 @@ var SpaceStateMachine = class {
1018
1597
  case "dxos.halo.invitations.CancelDelegatedInvitation":
1019
1598
  case "dxos.halo.invitations.DelegateSpaceInvitation": {
1020
1599
  if (!this._canInviteNewMembers(credential.issuer)) {
1021
- log2.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1022
- F: __dxlog_file4,
1023
- L: 238,
1600
+ log3.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1601
+ F: __dxlog_file5,
1602
+ L: 222,
1024
1603
  S: this,
1025
1604
  C: (f, a) => f(...a)
1026
1605
  });
@@ -1048,17 +1627,11 @@ var SpaceStateMachine = class {
1048
1627
  return true;
1049
1628
  }
1050
1629
  _canInviteNewMembers(key) {
1051
- return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember2.Role.ADMIN;
1630
+ return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember3.Role.ADMIN || this._members.getRole(key) === SpaceMember3.Role.OWNER;
1052
1631
  }
1053
1632
  _canAdmitFeeds(key) {
1054
1633
  const role = this._members.getRole(key);
1055
- return role === SpaceMember2.Role.MEMBER || role === SpaceMember2.Role.ADMIN;
1056
- }
1057
- _canRevokeCredentials(key) {
1058
- return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember2.Role.ADMIN;
1059
- }
1060
- _credentialCanBeRevoked(entry) {
1061
- return REVOCABLE_CREDENTIALS.includes(entry.credential.subject.assertion["@type"]);
1634
+ return role === SpaceMember3.Role.EDITOR || role === SpaceMember3.Role.ADMIN || role === SpaceMember3.Role.OWNER;
1062
1635
  }
1063
1636
  };
1064
1637
  var CredentialConsumer = class {
@@ -1091,24 +1664,24 @@ var CredentialConsumer = class {
1091
1664
 
1092
1665
  // packages/core/halo/credentials/src/processor/device-state-machine.ts
1093
1666
  import { Trigger } from "@dxos/async";
1094
- import { invariant as invariant4 } from "@dxos/invariant";
1095
- import { PublicKey as PublicKey7 } from "@dxos/keys";
1096
- import { log as log3 } from "@dxos/log";
1097
- import { ComplexMap as ComplexMap5 } from "@dxos/util";
1098
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1667
+ import { invariant as invariant5 } from "@dxos/invariant";
1668
+ import { PublicKey as PublicKey8 } from "@dxos/keys";
1669
+ import { log as log4 } from "@dxos/log";
1670
+ import { ComplexMap as ComplexMap6 } from "@dxos/util";
1671
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1099
1672
  var DeviceStateMachine = class {
1100
1673
  constructor(_params) {
1101
1674
  this._params = _params;
1102
- this.authorizedDeviceKeys = new ComplexMap5(PublicKey7.hash);
1675
+ this.authorizedDeviceKeys = new ComplexMap6(PublicKey8.hash);
1103
1676
  this.deviceChainReady = new Trigger();
1104
1677
  }
1105
1678
  async processCredential(credential) {
1106
- log3("processing credential...", {
1679
+ log4("processing credential...", {
1107
1680
  identityKey: this._params.identityKey,
1108
1681
  deviceKey: this._params.deviceKey,
1109
1682
  credential
1110
1683
  }, {
1111
- F: __dxlog_file5,
1684
+ F: __dxlog_file6,
1112
1685
  L: 35,
1113
1686
  S: this,
1114
1687
  C: (f, a) => f(...a)
@@ -1122,8 +1695,8 @@ var DeviceStateMachine = class {
1122
1695
  const assertion = getCredentialAssertion(credential);
1123
1696
  switch (assertion["@type"]) {
1124
1697
  case "dxos.halo.credentials.AuthorizedDevice": {
1125
- invariant4(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1126
- F: __dxlog_file5,
1698
+ invariant5(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1699
+ F: __dxlog_file6,
1127
1700
  L: 51,
1128
1701
  S: this,
1129
1702
  A: [
@@ -1132,12 +1705,12 @@ var DeviceStateMachine = class {
1132
1705
  ]
1133
1706
  });
1134
1707
  this.authorizedDeviceKeys.set(assertion.deviceKey, {});
1135
- log3("added device", {
1708
+ log4("added device", {
1136
1709
  localDeviceKey: this._params.deviceKey,
1137
1710
  deviceKey: assertion.deviceKey,
1138
1711
  size: this.authorizedDeviceKeys.size
1139
1712
  }, {
1140
- F: __dxlog_file5,
1713
+ F: __dxlog_file6,
1141
1714
  L: 54,
1142
1715
  S: this,
1143
1716
  C: (f, a) => f(...a)
@@ -1146,8 +1719,8 @@ var DeviceStateMachine = class {
1146
1719
  break;
1147
1720
  }
1148
1721
  case "dxos.halo.credentials.DeviceProfile": {
1149
- invariant4(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1150
- F: __dxlog_file5,
1722
+ invariant5(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1723
+ F: __dxlog_file6,
1151
1724
  L: 64,
1152
1725
  S: this,
1153
1726
  A: [
@@ -1156,11 +1729,11 @@ var DeviceStateMachine = class {
1156
1729
  ]
1157
1730
  });
1158
1731
  if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
1159
- log3.trace("dxos.halo.device", {
1732
+ log4.trace("dxos.halo.device", {
1160
1733
  deviceKey: credential.subject.id,
1161
1734
  profile: assertion.profile
1162
1735
  }, {
1163
- F: __dxlog_file5,
1736
+ F: __dxlog_file6,
1164
1737
  L: 67,
1165
1738
  S: this,
1166
1739
  C: (f, a) => f(...a)
@@ -1175,8 +1748,8 @@ var DeviceStateMachine = class {
1175
1748
  };
1176
1749
 
1177
1750
  // packages/core/halo/credentials/src/processor/profile-state-machine.ts
1178
- import { log as log4 } from "@dxos/log";
1179
- var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1751
+ import { log as log5 } from "@dxos/log";
1752
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1180
1753
  var ProfileStateMachine = class {
1181
1754
  constructor(_params) {
1182
1755
  this._params = _params;
@@ -1186,11 +1759,11 @@ var ProfileStateMachine = class {
1186
1759
  switch (assertion["@type"]) {
1187
1760
  case "dxos.halo.credentials.IdentityProfile": {
1188
1761
  if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
1189
- log4.warn("Invalid profile credential", {
1762
+ log5.warn("Invalid profile credential", {
1190
1763
  expectedIdentity: this._params.identityKey,
1191
1764
  credential
1192
1765
  }, {
1193
- F: __dxlog_file6,
1766
+ F: __dxlog_file7,
1194
1767
  L: 34,
1195
1768
  S: this,
1196
1769
  C: (f, a) => f(...a)
@@ -1198,11 +1771,11 @@ var ProfileStateMachine = class {
1198
1771
  return;
1199
1772
  }
1200
1773
  this.profile = assertion.profile;
1201
- log4("updated profile", {
1774
+ log5("updated profile", {
1202
1775
  identityKey: this._params.identityKey,
1203
1776
  profile: this.profile
1204
1777
  }, {
1205
- F: __dxlog_file6,
1778
+ F: __dxlog_file7,
1206
1779
  L: 40,
1207
1780
  S: this,
1208
1781
  C: (f, a) => f(...a)