@dxos/credentials 0.5.1-next.f040be1 → 0.5.2-main.80497e2

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
@@ -88,13 +88,18 @@ var import_util4 = require("@dxos/util");
88
88
  var import_invariant3 = require("@dxos/invariant");
89
89
  var import_keys6 = require("@dxos/keys");
90
90
  var import_log2 = require("@dxos/log");
91
+ var import_credentials3 = require("@dxos/protocols/proto/dxos/halo/credentials");
91
92
  var import_util5 = require("@dxos/util");
92
- var import_async2 = require("@dxos/async");
93
93
  var import_invariant4 = require("@dxos/invariant");
94
94
  var import_keys7 = require("@dxos/keys");
95
95
  var import_log3 = require("@dxos/log");
96
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");
97
100
  var import_log4 = require("@dxos/log");
101
+ var import_util7 = require("@dxos/util");
102
+ var import_log5 = require("@dxos/log");
98
103
  var defaultSecretProvider = async () => Buffer.from("0000");
99
104
  var defaultSecretValidator = async (invitation, secret) => true;
100
105
  var generatePasscode = (length = 4) => {
@@ -179,6 +184,9 @@ var checkCredentialType = (credential, type) => credential.subject.assertion["@t
179
184
  var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
180
185
  var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
181
186
  var verifyCredential = async (credential) => {
187
+ if (credential.parentCredentialIds?.length === 0) {
188
+ delete credential.parentCredentialIds;
189
+ }
182
190
  if (!credential.issuer.equals(credential.proof.signer)) {
183
191
  if (!credential.proof.chain) {
184
192
  return {
@@ -241,10 +249,10 @@ var verifyChain = async (chain, authority, subject) => {
241
249
  };
242
250
  };
243
251
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
244
- var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
252
+ var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
245
253
  (0, import_invariant.invariant)(assertion["@type"], "Invalid assertion.", {
246
254
  F: __dxlog_file,
247
- L: 45,
255
+ L: 48,
248
256
  S: void 0,
249
257
  A: [
250
258
  "assertion['@type']",
@@ -253,7 +261,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
253
261
  });
254
262
  (0, import_invariant.invariant)(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
255
263
  F: __dxlog_file,
256
- L: 46,
264
+ L: 49,
257
265
  S: void 0,
258
266
  A: [
259
267
  "!!signingKey === !!chain",
@@ -264,7 +272,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
264
272
  const result = await verifyChain(chain, issuer, signingKey);
265
273
  (0, import_invariant.invariant)(result.kind === "pass", "Invalid chain.", {
266
274
  F: __dxlog_file,
267
- L: 49,
275
+ L: 52,
268
276
  S: void 0,
269
277
  A: [
270
278
  "result.kind === 'pass'",
@@ -287,6 +295,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
287
295
  nonce
288
296
  }
289
297
  };
298
+ if ((parentCredentialIds?.length ?? 0) > 0) {
299
+ credential.parentCredentialIds = parentCredentialIds;
300
+ }
290
301
  const signedPayload = getCredentialProofPayload(credential);
291
302
  credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
292
303
  if (chain) {
@@ -303,12 +314,13 @@ var createCredentialMessage = (credential) => {
303
314
  };
304
315
  var createCredentialSignerWithKey = (signer, issuer) => ({
305
316
  getIssuer: () => issuer,
306
- createCredential: ({ subject, assertion, nonce }) => createCredential({
317
+ createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
307
318
  signer,
308
319
  issuer,
309
320
  subject,
310
321
  assertion,
311
- nonce
322
+ nonce,
323
+ parentCredentialIds
312
324
  })
313
325
  });
314
326
  var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
@@ -372,7 +384,7 @@ var CredentialGenerator = class {
372
384
  assertion: {
373
385
  "@type": "dxos.halo.credentials.SpaceMember",
374
386
  spaceKey,
375
- role: import_credentials.SpaceMember.Role.MEMBER,
387
+ role: import_credentials.SpaceMember.Role.EDITOR,
376
388
  genesisFeedKey
377
389
  }
378
390
  }),
@@ -467,14 +479,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
467
479
  credential
468
480
  }));
469
481
  };
470
- 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) => {
471
483
  const credentials = await Promise.all([
472
484
  await signer.createCredential({
473
485
  subject: identityKey,
486
+ parentCredentialIds: membershipChainHeads,
474
487
  assertion: {
475
488
  "@type": "dxos.halo.credentials.SpaceMember",
476
489
  spaceKey,
477
- role: import_credentials.SpaceMember.Role.ADMIN,
490
+ role,
478
491
  profile,
479
492
  genesisFeedKey,
480
493
  invitationCredentialId
@@ -735,21 +748,574 @@ var InvitationStateMachine = class {
735
748
  }
736
749
  }
737
750
  };
738
- 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";
739
1301
  var MemberStateMachine = class {
740
1302
  constructor(_spaceKey) {
741
1303
  this._spaceKey = _spaceKey;
742
- this._members = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
743
- 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;
744
1307
  }
745
1308
  get creator() {
746
- return this._creator;
1309
+ return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
747
1310
  }
748
1311
  get members() {
749
- return this._members;
1312
+ return this._hashgraph.getState();
1313
+ }
1314
+ get membershipChainHeads() {
1315
+ return this._hashgraph.getLeafIds();
750
1316
  }
751
1317
  getRole(member) {
752
- return this._members.get(member)?.assertion.role;
1318
+ return this._getRole(this._hashgraph.getGlobalStateScope(), member);
753
1319
  }
754
1320
  /**
755
1321
  * Processes the SpaceMember credential.
@@ -760,84 +1326,122 @@ var MemberStateMachine = class {
760
1326
  switch (assertion["@type"]) {
761
1327
  case "dxos.halo.credentials.SpaceMember": {
762
1328
  (0, import_invariant3.invariant)(assertion.spaceKey.equals(this._spaceKey), void 0, {
763
- F: __dxlog_file3,
764
- L: 58,
1329
+ F: __dxlog_file4,
1330
+ L: 66,
765
1331
  S: this,
766
1332
  A: [
767
1333
  "assertion.spaceKey.equals(this._spaceKey)",
768
1334
  ""
769
1335
  ]
770
1336
  });
771
- if (this._members.has(credential.subject.id)) {
772
- return;
1337
+ if (this._ownerKey == null && credential.issuer === this._spaceKey) {
1338
+ this._ownerKey = credential.subject.id;
773
1339
  }
774
- const info = {
775
- key: credential.subject.id,
776
- credential,
777
- assertion,
778
- removed: false,
779
- profile: assertion.profile
780
- };
781
- if (!this._creator) {
782
- this._creator = info;
1340
+ if (assertion.profile != null) {
1341
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
783
1342
  }
784
- this._members.set(credential.subject.id, info);
785
- (0, import_log2.log)("member added", {
786
- member: credential.subject.id,
787
- space: this._spaceKey,
788
- role: assertion.role,
789
- profile: assertion.profile
790
- }, {
791
- F: __dxlog_file3,
792
- L: 79,
793
- S: this,
794
- C: (f, a) => f(...a)
795
- });
796
- await this.onMemberAdmitted.callIfSet(info);
1343
+ await this._hashgraph.addVertex(credential, assertion);
797
1344
  break;
798
1345
  }
799
1346
  case "dxos.halo.credentials.MemberProfile": {
800
- const member = this._members.get(credential.subject.id);
801
- if (!member) {
1347
+ const member = this._hashgraph.getSubjectState(credential.subject.id);
1348
+ if (member) {
1349
+ member.profile = assertion.profile;
1350
+ } else {
802
1351
  import_log2.log.warn("Member not found", {
803
1352
  id: credential.subject.id
804
1353
  }, {
805
- F: __dxlog_file3,
806
- L: 93,
1354
+ F: __dxlog_file4,
1355
+ L: 81,
807
1356
  S: this,
808
1357
  C: (f, a) => f(...a)
809
1358
  });
810
- } else {
811
- member.profile = assertion.profile;
812
1359
  }
1360
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
813
1361
  break;
814
1362
  }
815
1363
  default:
816
1364
  throw new Error("Invalid assertion type");
817
1365
  }
818
1366
  }
819
- async onRevoked(revoked, revocation) {
820
- (0, import_invariant3.invariant)(revoked.id, void 0, {
821
- F: __dxlog_file3,
822
- L: 106,
823
- S: this,
824
- A: [
825
- "revoked.id",
826
- ""
827
- ]
828
- });
829
- for (const member of this._members.values()) {
830
- if (member.credential.id?.equals(revoked.id)) {
831
- member.removed = true;
832
- 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;
833
1439
  }
834
1440
  }
1441
+ return realRole;
835
1442
  }
836
1443
  };
837
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
838
- var REVOCABLE_CREDENTIALS = [
839
- "dxos.halo.credentials.SpaceMember"
840
- ];
1444
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
841
1445
  var SpaceStateMachine = class {
842
1446
  constructor(_spaceKey) {
843
1447
  this._spaceKey = _spaceKey;
@@ -849,7 +1453,7 @@ var SpaceStateMachine = class {
849
1453
  this._processedCredentials = new import_util2.ComplexSet(import_keys3.PublicKey.hash);
850
1454
  this._credentialProcessors = [];
851
1455
  this.onCredentialProcessed = new import_util2.Callback();
852
- this.onMemberAdmitted = this._members.onMemberAdmitted;
1456
+ this.onMemberRoleChanged = this._members.onMemberRoleChanged;
853
1457
  this.onFeedAdmitted = this._feeds.onFeedAdmitted;
854
1458
  this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
855
1459
  this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
@@ -860,6 +1464,9 @@ var SpaceStateMachine = class {
860
1464
  get members() {
861
1465
  return this._members.members;
862
1466
  }
1467
+ get membershipChainHeads() {
1468
+ return this._members.membershipChainHeads;
1469
+ }
863
1470
  get feeds() {
864
1471
  return this._feeds.feeds;
865
1472
  }
@@ -912,8 +1519,8 @@ var SpaceStateMachine = class {
912
1519
  const result = await verifyCredential(credential);
913
1520
  if (result.kind !== "pass") {
914
1521
  import_log.log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
915
- F: __dxlog_file4,
916
- L: 149,
1522
+ F: __dxlog_file5,
1523
+ L: 152,
917
1524
  S: this,
918
1525
  C: (f, a) => f(...a)
919
1526
  });
@@ -922,43 +1529,11 @@ var SpaceStateMachine = class {
922
1529
  }
923
1530
  const assertion = getCredentialAssertion(credential);
924
1531
  switch (assertion["@type"]) {
925
- case "dxos.halo.credentials.Revocation": {
926
- if (!this._canRevokeCredentials(credential.issuer)) {
927
- import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
928
- F: __dxlog_file4,
929
- L: 158,
930
- S: this,
931
- C: (f, a) => f(...a)
932
- });
933
- return false;
934
- }
935
- const toBeRevoked = this._credentialsById.get(credential.subject.id);
936
- if (!toBeRevoked) {
937
- import_log.log.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
938
- F: __dxlog_file4,
939
- L: 163,
940
- S: this,
941
- C: (f, a) => f(...a)
942
- });
943
- return false;
944
- }
945
- if (!this._credentialCanBeRevoked(toBeRevoked)) {
946
- import_log.log.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
947
- F: __dxlog_file4,
948
- L: 167,
949
- S: this,
950
- C: (f, a) => f(...a)
951
- });
952
- }
953
- toBeRevoked.revoked = true;
954
- await this._members.onRevoked(toBeRevoked.credential, credential);
955
- break;
956
- }
957
1532
  case "dxos.halo.credentials.SpaceGenesis": {
958
1533
  if (this._genesisCredential) {
959
1534
  import_log.log.warn("Space already has a genesis credential.", void 0, {
960
- F: __dxlog_file4,
961
- L: 176,
1535
+ F: __dxlog_file5,
1536
+ L: 161,
962
1537
  S: this,
963
1538
  C: (f, a) => f(...a)
964
1539
  });
@@ -966,8 +1541,8 @@ var SpaceStateMachine = class {
966
1541
  }
967
1542
  if (!credential.issuer.equals(this._spaceKey)) {
968
1543
  import_log.log.warn("Space genesis credential must be issued by space.", void 0, {
969
- F: __dxlog_file4,
970
- L: 180,
1544
+ F: __dxlog_file5,
1545
+ L: 165,
971
1546
  S: this,
972
1547
  C: (f, a) => f(...a)
973
1548
  });
@@ -975,8 +1550,8 @@ var SpaceStateMachine = class {
975
1550
  }
976
1551
  if (!credential.subject.id.equals(this._spaceKey)) {
977
1552
  import_log.log.warn("Space genesis credential must be issued to space.", void 0, {
978
- F: __dxlog_file4,
979
- L: 184,
1553
+ F: __dxlog_file5,
1554
+ L: 169,
980
1555
  S: this,
981
1556
  C: (f, a) => f(...a)
982
1557
  });
@@ -991,8 +1566,8 @@ var SpaceStateMachine = class {
991
1566
  }
992
1567
  if (!this._genesisCredential) {
993
1568
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
994
- F: __dxlog_file4,
995
- L: 198,
1569
+ F: __dxlog_file5,
1570
+ L: 182,
996
1571
  S: this,
997
1572
  C: (f, a) => f(...a)
998
1573
  });
@@ -1000,8 +1575,8 @@ var SpaceStateMachine = class {
1000
1575
  }
1001
1576
  if (!this._canInviteNewMembers(credential.issuer)) {
1002
1577
  import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1003
- F: __dxlog_file4,
1004
- L: 202,
1578
+ F: __dxlog_file5,
1579
+ L: 186,
1005
1580
  S: this,
1006
1581
  C: (f, a) => f(...a)
1007
1582
  });
@@ -1014,8 +1589,8 @@ var SpaceStateMachine = class {
1014
1589
  case "dxos.halo.credentials.MemberProfile": {
1015
1590
  if (!this._genesisCredential) {
1016
1591
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
1017
- F: __dxlog_file4,
1018
- L: 213,
1592
+ F: __dxlog_file5,
1593
+ L: 197,
1019
1594
  S: this,
1020
1595
  C: (f, a) => f(...a)
1021
1596
  });
@@ -1027,8 +1602,8 @@ var SpaceStateMachine = class {
1027
1602
  case "dxos.halo.credentials.AdmittedFeed": {
1028
1603
  if (!this._genesisCredential) {
1029
1604
  import_log.log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
1030
- F: __dxlog_file4,
1031
- L: 223,
1605
+ F: __dxlog_file5,
1606
+ L: 207,
1032
1607
  S: this,
1033
1608
  C: (f, a) => f(...a)
1034
1609
  });
@@ -1036,8 +1611,8 @@ var SpaceStateMachine = class {
1036
1611
  }
1037
1612
  if (!this._canAdmitFeeds(credential.issuer)) {
1038
1613
  import_log.log.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
1039
- F: __dxlog_file4,
1040
- L: 227,
1614
+ F: __dxlog_file5,
1615
+ L: 211,
1041
1616
  S: this,
1042
1617
  C: (f, a) => f(...a)
1043
1618
  });
@@ -1050,8 +1625,8 @@ var SpaceStateMachine = class {
1050
1625
  case "dxos.halo.invitations.DelegateSpaceInvitation": {
1051
1626
  if (!this._canInviteNewMembers(credential.issuer)) {
1052
1627
  import_log.log.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1053
- F: __dxlog_file4,
1054
- L: 238,
1628
+ F: __dxlog_file5,
1629
+ L: 222,
1055
1630
  S: this,
1056
1631
  C: (f, a) => f(...a)
1057
1632
  });
@@ -1079,17 +1654,11 @@ var SpaceStateMachine = class {
1079
1654
  return true;
1080
1655
  }
1081
1656
  _canInviteNewMembers(key) {
1082
- 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;
1083
1658
  }
1084
1659
  _canAdmitFeeds(key) {
1085
1660
  const role = this._members.getRole(key);
1086
- return role === import_credentials2.SpaceMember.Role.MEMBER || role === import_credentials2.SpaceMember.Role.ADMIN;
1087
- }
1088
- _canRevokeCredentials(key) {
1089
- return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
1090
- }
1091
- _credentialCanBeRevoked(entry) {
1092
- 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;
1093
1662
  }
1094
1663
  };
1095
1664
  var CredentialConsumer = class {
@@ -1119,20 +1688,20 @@ var CredentialConsumer = class {
1119
1688
  await this._onClose();
1120
1689
  }
1121
1690
  };
1122
- 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";
1123
1692
  var DeviceStateMachine = class {
1124
1693
  constructor(_params) {
1125
1694
  this._params = _params;
1126
- this.authorizedDeviceKeys = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1695
+ this.authorizedDeviceKeys = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
1127
1696
  this.deviceChainReady = new import_async2.Trigger();
1128
1697
  }
1129
1698
  async processCredential(credential) {
1130
- (0, import_log3.log)("processing credential...", {
1699
+ (0, import_log4.log)("processing credential...", {
1131
1700
  identityKey: this._params.identityKey,
1132
1701
  deviceKey: this._params.deviceKey,
1133
1702
  credential
1134
1703
  }, {
1135
- F: __dxlog_file5,
1704
+ F: __dxlog_file6,
1136
1705
  L: 35,
1137
1706
  S: this,
1138
1707
  C: (f, a) => f(...a)
@@ -1146,8 +1715,8 @@ var DeviceStateMachine = class {
1146
1715
  const assertion = getCredentialAssertion(credential);
1147
1716
  switch (assertion["@type"]) {
1148
1717
  case "dxos.halo.credentials.AuthorizedDevice": {
1149
- (0, import_invariant4.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1150
- F: __dxlog_file5,
1718
+ (0, import_invariant5.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1719
+ F: __dxlog_file6,
1151
1720
  L: 51,
1152
1721
  S: this,
1153
1722
  A: [
@@ -1156,12 +1725,12 @@ var DeviceStateMachine = class {
1156
1725
  ]
1157
1726
  });
1158
1727
  this.authorizedDeviceKeys.set(assertion.deviceKey, {});
1159
- (0, import_log3.log)("added device", {
1728
+ (0, import_log4.log)("added device", {
1160
1729
  localDeviceKey: this._params.deviceKey,
1161
1730
  deviceKey: assertion.deviceKey,
1162
1731
  size: this.authorizedDeviceKeys.size
1163
1732
  }, {
1164
- F: __dxlog_file5,
1733
+ F: __dxlog_file6,
1165
1734
  L: 54,
1166
1735
  S: this,
1167
1736
  C: (f, a) => f(...a)
@@ -1170,8 +1739,8 @@ var DeviceStateMachine = class {
1170
1739
  break;
1171
1740
  }
1172
1741
  case "dxos.halo.credentials.DeviceProfile": {
1173
- (0, import_invariant4.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1174
- F: __dxlog_file5,
1742
+ (0, import_invariant5.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1743
+ F: __dxlog_file6,
1175
1744
  L: 64,
1176
1745
  S: this,
1177
1746
  A: [
@@ -1180,11 +1749,11 @@ var DeviceStateMachine = class {
1180
1749
  ]
1181
1750
  });
1182
1751
  if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
1183
- import_log3.log.trace("dxos.halo.device", {
1752
+ import_log4.log.trace("dxos.halo.device", {
1184
1753
  deviceKey: credential.subject.id,
1185
1754
  profile: assertion.profile
1186
1755
  }, {
1187
- F: __dxlog_file5,
1756
+ F: __dxlog_file6,
1188
1757
  L: 67,
1189
1758
  S: this,
1190
1759
  C: (f, a) => f(...a)
@@ -1197,7 +1766,7 @@ var DeviceStateMachine = class {
1197
1766
  }
1198
1767
  }
1199
1768
  };
1200
- 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";
1201
1770
  var ProfileStateMachine = class {
1202
1771
  constructor(_params) {
1203
1772
  this._params = _params;
@@ -1207,11 +1776,11 @@ var ProfileStateMachine = class {
1207
1776
  switch (assertion["@type"]) {
1208
1777
  case "dxos.halo.credentials.IdentityProfile": {
1209
1778
  if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
1210
- import_log4.log.warn("Invalid profile credential", {
1779
+ import_log5.log.warn("Invalid profile credential", {
1211
1780
  expectedIdentity: this._params.identityKey,
1212
1781
  credential
1213
1782
  }, {
1214
- F: __dxlog_file6,
1783
+ F: __dxlog_file7,
1215
1784
  L: 34,
1216
1785
  S: this,
1217
1786
  C: (f, a) => f(...a)
@@ -1219,11 +1788,11 @@ var ProfileStateMachine = class {
1219
1788
  return;
1220
1789
  }
1221
1790
  this.profile = assertion.profile;
1222
- (0, import_log4.log)("updated profile", {
1791
+ (0, import_log5.log)("updated profile", {
1223
1792
  identityKey: this._params.identityKey,
1224
1793
  profile: this.profile
1225
1794
  }, {
1226
- F: __dxlog_file6,
1795
+ F: __dxlog_file7,
1227
1796
  L: 40,
1228
1797
  S: this,
1229
1798
  C: (f, a) => f(...a)