@dxos/credentials 0.5.1-next.f040be1 → 0.5.2-main.16cd857

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 (36) hide show
  1. package/dist/lib/browser/index.mjs +750 -168
  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 +723 -145
  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/signing.d.ts.map +1 -1
  12. package/dist/types/src/credentials/verifier.d.ts +1 -1
  13. package/dist/types/src/credentials/verifier.d.ts.map +1 -1
  14. package/dist/types/src/graph/credential-graph.d.ts +152 -0
  15. package/dist/types/src/graph/credential-graph.d.ts.map +1 -0
  16. package/dist/types/src/presentations/signing.d.ts.map +1 -1
  17. package/dist/types/src/state-machine/member-state-machine.d.ts +17 -12
  18. package/dist/types/src/state-machine/member-state-machine.d.ts.map +1 -1
  19. package/dist/types/src/state-machine/member-state-machine.test.d.ts +2 -0
  20. package/dist/types/src/state-machine/member-state-machine.test.d.ts.map +1 -0
  21. package/dist/types/src/state-machine/space-state-machine.d.ts +3 -3
  22. package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
  23. package/package.json +16 -16
  24. package/src/credentials/credential-factory.ts +6 -1
  25. package/src/credentials/credential-generator.ts +15 -2
  26. package/src/credentials/signing.ts +3 -0
  27. package/src/credentials/verifier.test.ts +25 -0
  28. package/src/credentials/verifier.ts +5 -1
  29. package/src/graph/credential-graph.ts +557 -0
  30. package/src/presentations/signing.ts +7 -1
  31. package/src/presentations/verifier.test.ts +46 -0
  32. package/src/state-machine/invitation-state-machine.test.ts +1 -1
  33. package/src/state-machine/member-state-machine.test.ts +580 -0
  34. package/src/state-machine/member-state-machine.ts +111 -53
  35. package/src/state-machine/space-state-machine.test.ts +20 -16
  36. 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) => {
@@ -113,6 +118,9 @@ var getCredentialProofPayload = (credential) => {
113
118
  chain: void 0
114
119
  }
115
120
  };
121
+ if (copy.parentCredentialIds?.length === 0) {
122
+ delete copy.parentCredentialIds;
123
+ }
116
124
  delete copy.id;
117
125
  return Buffer.from(canonicalStringify(copy));
118
126
  };
@@ -161,7 +169,15 @@ var truncateKey = (key) => {
161
169
  };
162
170
  var getPresentationProofPayload = (credentials, proof) => {
163
171
  const copy = {
164
- credentials,
172
+ credentials: credentials.map((credential) => {
173
+ const copy2 = {
174
+ ...credential
175
+ };
176
+ if (copy2.parentCredentialIds?.length === 0) {
177
+ delete copy2.parentCredentialIds;
178
+ }
179
+ return copy2;
180
+ }),
165
181
  proof: {
166
182
  ...proof,
167
183
  value: new Uint8Array(),
@@ -179,6 +195,9 @@ var checkCredentialType = (credential, type) => credential.subject.assertion["@t
179
195
  var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
180
196
  var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
181
197
  var verifyCredential = async (credential) => {
198
+ if (credential.parentCredentialIds?.length === 0) {
199
+ delete credential.parentCredentialIds;
200
+ }
182
201
  if (!credential.issuer.equals(credential.proof.signer)) {
183
202
  if (!credential.proof.chain) {
184
203
  return {
@@ -241,10 +260,10 @@ var verifyChain = async (chain, authority, subject) => {
241
260
  };
242
261
  };
243
262
  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 }) => {
263
+ var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
245
264
  (0, import_invariant.invariant)(assertion["@type"], "Invalid assertion.", {
246
265
  F: __dxlog_file,
247
- L: 45,
266
+ L: 48,
248
267
  S: void 0,
249
268
  A: [
250
269
  "assertion['@type']",
@@ -253,7 +272,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
253
272
  });
254
273
  (0, import_invariant.invariant)(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
255
274
  F: __dxlog_file,
256
- L: 46,
275
+ L: 49,
257
276
  S: void 0,
258
277
  A: [
259
278
  "!!signingKey === !!chain",
@@ -264,7 +283,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
264
283
  const result = await verifyChain(chain, issuer, signingKey);
265
284
  (0, import_invariant.invariant)(result.kind === "pass", "Invalid chain.", {
266
285
  F: __dxlog_file,
267
- L: 49,
286
+ L: 52,
268
287
  S: void 0,
269
288
  A: [
270
289
  "result.kind === 'pass'",
@@ -279,6 +298,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
279
298
  id: subject,
280
299
  assertion
281
300
  },
301
+ parentCredentialIds,
282
302
  proof: {
283
303
  type: SIGNATURE_TYPE_ED25519,
284
304
  creationDate: /* @__PURE__ */ new Date(),
@@ -303,12 +323,13 @@ var createCredentialMessage = (credential) => {
303
323
  };
304
324
  var createCredentialSignerWithKey = (signer, issuer) => ({
305
325
  getIssuer: () => issuer,
306
- createCredential: ({ subject, assertion, nonce }) => createCredential({
326
+ createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
307
327
  signer,
308
328
  issuer,
309
329
  subject,
310
330
  assertion,
311
- nonce
331
+ nonce,
332
+ parentCredentialIds
312
333
  })
313
334
  });
314
335
  var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
@@ -372,7 +393,7 @@ var CredentialGenerator = class {
372
393
  assertion: {
373
394
  "@type": "dxos.halo.credentials.SpaceMember",
374
395
  spaceKey,
375
- role: import_credentials.SpaceMember.Role.MEMBER,
396
+ role: import_credentials.SpaceMember.Role.EDITOR,
376
397
  genesisFeedKey
377
398
  }
378
399
  }),
@@ -467,14 +488,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
467
488
  credential
468
489
  }));
469
490
  };
470
- var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, profile, invitationCredentialId) => {
491
+ var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = import_credentials.SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId) => {
471
492
  const credentials = await Promise.all([
472
493
  await signer.createCredential({
473
494
  subject: identityKey,
495
+ parentCredentialIds: membershipChainHeads,
474
496
  assertion: {
475
497
  "@type": "dxos.halo.credentials.SpaceMember",
476
498
  spaceKey,
477
- role: import_credentials.SpaceMember.Role.ADMIN,
499
+ role,
478
500
  profile,
479
501
  genesisFeedKey,
480
502
  invitationCredentialId
@@ -735,21 +757,574 @@ var InvitationStateMachine = class {
735
757
  }
736
758
  }
737
759
  };
738
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
760
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
761
+ var CredentialGraph = class {
762
+ constructor(_stateHandler) {
763
+ this._stateHandler = _stateHandler;
764
+ this._vertexIdGenerator = 1;
765
+ this._root = {
766
+ id: -1,
767
+ parents: [],
768
+ children: []
769
+ };
770
+ this._sentinel = {
771
+ id: -2,
772
+ parents: [],
773
+ children: []
774
+ };
775
+ this._vertexByCredentialId = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
776
+ this._subjectToVertex = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
777
+ this._subjectToState = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
778
+ this.onSubjectStateChanged = new import_util6.Callback();
779
+ }
780
+ getSubjectState(subjectId) {
781
+ return this._subjectToState.get(subjectId);
782
+ }
783
+ getState() {
784
+ return this._subjectToState;
785
+ }
786
+ getLeafIds() {
787
+ return this._sentinel.parents.map((v) => v.credential.id);
788
+ }
789
+ getGlobalStateScope() {
790
+ return {
791
+ state: this._subjectToVertex
792
+ };
793
+ }
794
+ addVertex(credential, assertion) {
795
+ const newVertex = {
796
+ id: this._vertexIdGenerator++,
797
+ credential,
798
+ assertion,
799
+ parents: [],
800
+ children: []
801
+ };
802
+ this._vertexByCredentialId.set(credential.id, newVertex);
803
+ const parentIds = credential.parentCredentialIds ?? [];
804
+ if (parentIds.length === 0) {
805
+ this._root.children.push(newVertex);
806
+ newVertex.parents.push(this._root);
807
+ } else {
808
+ for (const parentId of parentIds) {
809
+ const parentVertex = this._vertexByCredentialId.get(parentId);
810
+ if (parentVertex == null) {
811
+ import_log3.log.error("credential skipped because of the unknown parent", {
812
+ credential,
813
+ parentId
814
+ }, {
815
+ F: __dxlog_file3,
816
+ L: 72,
817
+ S: this,
818
+ C: (f, a) => f(...a)
819
+ });
820
+ continue;
821
+ }
822
+ parentVertex.children.push(newVertex);
823
+ newVertex.parents.push(parentVertex);
824
+ this._removeSentinelConnection(parentVertex);
825
+ }
826
+ }
827
+ newVertex.children.push(this._sentinel);
828
+ this._sentinel.parents.push(newVertex);
829
+ return this._onVertexInserted(newVertex);
830
+ }
831
+ _removeSentinelConnection(vertex) {
832
+ const sentinelIdx = vertex.children.indexOf(this._sentinel);
833
+ if (sentinelIdx >= 0) {
834
+ vertex.children.splice(sentinelIdx, 1);
835
+ const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
836
+ (0, import_invariant4.invariant)(vertexInSentinelIdx >= 0, void 0, {
837
+ F: __dxlog_file3,
838
+ L: 90,
839
+ S: this,
840
+ A: [
841
+ "vertexInSentinelIdx >= 0",
842
+ ""
843
+ ]
844
+ });
845
+ this._sentinel.parents.splice(vertexInSentinelIdx, 1);
846
+ }
847
+ }
848
+ async _onVertexInserted(newVertex) {
849
+ const { credential, assertion } = newVertex;
850
+ (0, import_invariant4.invariant)(credential, void 0, {
851
+ F: __dxlog_file3,
852
+ L: 97,
853
+ S: this,
854
+ A: [
855
+ "credential",
856
+ ""
857
+ ]
858
+ });
859
+ let changedSubjects = [];
860
+ const isUpdateAppliedOnTopOfThePreviousState = this._sentinel.parents.length === 1;
861
+ if (isUpdateAppliedOnTopOfThePreviousState) {
862
+ const subjectId = credential.subject.id;
863
+ if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
864
+ const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
865
+ const prevSubjectState = this._subjectToState.get(subjectId);
866
+ this._subjectToState.set(subjectId, newSubjectState);
867
+ this._subjectToVertex.set(subjectId, newVertex);
868
+ if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) {
869
+ changedSubjects.push(newSubjectState);
870
+ }
871
+ }
872
+ } else {
873
+ changedSubjects = this._recomputeState();
874
+ }
875
+ if (changedSubjects.length > 0) {
876
+ await this.onSubjectStateChanged.callIfSet(changedSubjects);
877
+ }
878
+ }
879
+ /**
880
+ * DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
881
+ * Continue after all paths leading to a merge point converge by merging their states.
882
+ * In case of a concurrent update paths are replayed taking into account the state set
883
+ * by the winning branch.
884
+ */
885
+ _recomputeState() {
886
+ const pendingPaths = /* @__PURE__ */ new Map();
887
+ const paths = [
888
+ this._createRootPath()
889
+ ];
890
+ let lastPath = null;
891
+ while (lastPath == null) {
892
+ const path = paths.pop();
893
+ (0, import_log3.log)("visit vertex", {
894
+ id: path.head.id
895
+ }, {
896
+ F: __dxlog_file3,
897
+ L: 132,
898
+ S: this,
899
+ C: (f, a) => f(...a)
900
+ });
901
+ this._updatePathState(path);
902
+ const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
903
+ if (convergedPaths == null) {
904
+ (0, import_log3.log)("waiting for other paths", void 0, {
905
+ F: __dxlog_file3,
906
+ L: 136,
907
+ S: this,
908
+ C: (f, a) => f(...a)
909
+ });
910
+ continue;
911
+ }
912
+ const mergeResult = this._mergePaths(convergedPaths);
913
+ if (mergeResult.type === "replay_required") {
914
+ this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
915
+ continue;
916
+ }
917
+ const merged = mergeResult.path;
918
+ if (merged.head.children.length === 0) {
919
+ lastPath = merged;
920
+ } else if (merged.head.children.length === 1) {
921
+ merged.head = merged.head.children[0];
922
+ paths.push(merged);
923
+ } else {
924
+ this._forkTraversal(paths, merged);
925
+ }
926
+ }
927
+ if (paths.length > 0) {
928
+ import_log3.log.error("traversal finished while there were active paths", {
929
+ paths: paths.map((p) => ({
930
+ path: toChosenPath(p),
931
+ head: p.head.id
932
+ }))
933
+ }, {
934
+ F: __dxlog_file3,
935
+ L: 155,
936
+ S: this,
937
+ C: (f, a) => f(...a)
938
+ });
939
+ }
940
+ return this._setCurrentState(lastPath);
941
+ }
942
+ _replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
943
+ paths.push(...mergeResult.replay.map((path) => {
944
+ const stateOverrides = path.stateOverrides ?? new import_util6.ComplexMap(import_keys7.PublicKey.hash);
945
+ mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
946
+ return {
947
+ ...mergeResult.from,
948
+ chosenPath: path.chosenPath,
949
+ stateOverrides
950
+ };
951
+ }));
952
+ (0, import_log3.log)("replay paths", () => ({
953
+ count: paths.length,
954
+ paths: paths.map((path) => ({
955
+ from: mergeResult.from.head.id,
956
+ path: toChosenPath(path),
957
+ overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
958
+ }))
959
+ }), {
960
+ F: __dxlog_file3,
961
+ L: 175,
962
+ S: this,
963
+ C: (f, a) => f(...a)
964
+ });
965
+ const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
966
+ pendingPaths.set(convergedPaths[0].head.id, clearedPending);
967
+ }
968
+ _handleMergePoint(paths, pendingPaths, path) {
969
+ const pendingList = pendingPaths.get(path.head.id) ?? [];
970
+ pendingPaths.set(path.head.id, pendingList);
971
+ pendingList.push(path);
972
+ if (pendingList.length < path.head.parents.length) {
973
+ return null;
974
+ }
975
+ if (path.head.id === this._sentinel.id && paths.length > 0) {
976
+ (0, import_log3.log)("waiting for all the active paths to converge on the sentinel", void 0, {
977
+ F: __dxlog_file3,
978
+ L: 199,
979
+ S: this,
980
+ C: (f, a) => f(...a)
981
+ });
982
+ return null;
983
+ }
984
+ pendingPaths.delete(path.head.id);
985
+ return pendingList;
986
+ }
987
+ _updatePathState(path) {
988
+ const headCredential = path.head.credential;
989
+ if (headCredential == null) {
990
+ return;
991
+ }
992
+ const updatedSubject = headCredential.subject.id;
993
+ path.credentials.add(headCredential.id);
994
+ let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
995
+ if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
996
+ const globalState = this.getGlobalStateScope();
997
+ isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
998
+ }
999
+ if (isUpdateAllowed) {
1000
+ path.forkChangedSubjects.add(updatedSubject);
1001
+ path.forkIssuers.add(headCredential.issuer);
1002
+ path.state.set(updatedSubject, path.head);
1003
+ (0, import_log3.log)("path state updated", () => ({
1004
+ subject: updatedSubject,
1005
+ newState: this._stateHandler.toLogString(path.head.assertion)
1006
+ }), {
1007
+ F: __dxlog_file3,
1008
+ L: 223,
1009
+ S: this,
1010
+ C: (f, a) => f(...a)
1011
+ });
1012
+ }
1013
+ }
1014
+ _forkTraversal(paths, path) {
1015
+ const replayChoice = path.chosenPath?.[path.head.id];
1016
+ const choices = replayChoice ?? path.head.children;
1017
+ for (const choice of choices) {
1018
+ (0, import_log3.log)("edge traversal", {
1019
+ from: path.head.id,
1020
+ to: choice.id
1021
+ }, {
1022
+ F: __dxlog_file3,
1023
+ L: 234,
1024
+ S: this,
1025
+ C: (f, a) => f(...a)
1026
+ });
1027
+ const fork = {
1028
+ forkPoint: path,
1029
+ chosenPath: {
1030
+ ...path.chosenPath,
1031
+ [path.head.id]: [
1032
+ choice
1033
+ ]
1034
+ },
1035
+ head: choice,
1036
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, path.credentials),
1037
+ state: new import_util6.ComplexMap(import_keys7.PublicKey.hash, [
1038
+ ...path.state.entries()
1039
+ ]),
1040
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1041
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1042
+ stateOverrides: path.stateOverrides
1043
+ };
1044
+ paths.push(fork);
1045
+ }
1046
+ }
1047
+ /**
1048
+ * Updates the current graph state.
1049
+ * @returns changed states.
1050
+ */
1051
+ _setCurrentState(path) {
1052
+ const changedSubjects = [];
1053
+ const newStateMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1054
+ const newVertexMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1055
+ for (const [subjectKey, subjectVertex] of path.state.entries()) {
1056
+ const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
1057
+ const prevState = this._subjectToState.get(subjectKey);
1058
+ newStateMap.set(subjectKey, newState);
1059
+ newVertexMap.set(subjectKey, subjectVertex);
1060
+ if (this._stateHandler.hasStateChanged(newState, prevState)) {
1061
+ changedSubjects.push(newState);
1062
+ }
1063
+ }
1064
+ this._subjectToState = newStateMap;
1065
+ this._subjectToVertex = newVertexMap;
1066
+ return changedSubjects;
1067
+ }
1068
+ /*
1069
+ * Walk up all the fork points and return the first one present in all the paths.
1070
+ * We use local id to determine vertex position in the graph, because nodes can't
1071
+ * be inserted in the middle (between a parent and a child) and ids are monotonically increasing.
1072
+ */
1073
+ _leastCommonAncestor(paths) {
1074
+ const uniqueForkPoints = paths.reduce((acc, path) => {
1075
+ let it = path.forkPoint;
1076
+ while (it) {
1077
+ acc.set(it.head.id, it);
1078
+ it = it.forkPoint;
1079
+ }
1080
+ return acc;
1081
+ }, /* @__PURE__ */ new Map());
1082
+ let maxId = this._root.id;
1083
+ let maxState = null;
1084
+ for (const [id, state] of uniqueForkPoints.entries()) {
1085
+ const headCredential = state.head.credential;
1086
+ if (headCredential != null) {
1087
+ const isPointInEveryPath = paths.every((p) => p.credentials.has(headCredential.id));
1088
+ if (isPointInEveryPath && id > maxId) {
1089
+ maxId = id;
1090
+ maxState = state;
1091
+ }
1092
+ }
1093
+ }
1094
+ return maxState ?? this._createRootPath();
1095
+ }
1096
+ /**
1097
+ * We might be merging paths where some of them had fork points after the initial forking.
1098
+ * We need all the paths to point to the least common fork point and contain all the changes
1099
+ * that happened after it.
1100
+ */
1101
+ _moveUpToForkPoint(forkPoint, path) {
1102
+ const isForkPointInPath = path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null;
1103
+ if (isForkPointInPath) {
1104
+ return path;
1105
+ }
1106
+ if (forkPoint.head.id === path.forkPoint?.head.id) {
1107
+ return path;
1108
+ }
1109
+ let it = path.forkPoint;
1110
+ while (it.head.id !== forkPoint.head.id) {
1111
+ it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
1112
+ it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
1113
+ it = it.forkPoint;
1114
+ path.forkPoint = it;
1115
+ }
1116
+ return path;
1117
+ }
1118
+ _mergePaths(convergedPaths) {
1119
+ (0, import_invariant4.invariant)(convergedPaths.length >= 1, void 0, {
1120
+ F: __dxlog_file3,
1121
+ L: 324,
1122
+ S: this,
1123
+ A: [
1124
+ "convergedPaths.length >= 1",
1125
+ ""
1126
+ ]
1127
+ });
1128
+ if (convergedPaths.length === 1) {
1129
+ return {
1130
+ type: "merged",
1131
+ path: convergedPaths[0]
1132
+ };
1133
+ }
1134
+ const forkPoint = this._leastCommonAncestor(convergedPaths);
1135
+ (0, import_log3.log)("merging paths", () => ({
1136
+ forkPointId: forkPoint.head.id,
1137
+ pathCount: convergedPaths.length,
1138
+ forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
1139
+ }), {
1140
+ F: __dxlog_file3,
1141
+ L: 329,
1142
+ S: this,
1143
+ C: (f, a) => f(...a)
1144
+ });
1145
+ const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
1146
+ (0, import_invariant4.invariant)(forkPoint, void 0, {
1147
+ F: __dxlog_file3,
1148
+ L: 335,
1149
+ S: this,
1150
+ A: [
1151
+ "forkPoint",
1152
+ ""
1153
+ ]
1154
+ });
1155
+ const result = {
1156
+ forkPoint: forkPoint.forkPoint,
1157
+ chosenPath: {
1158
+ ...forkPoint.chosenPath,
1159
+ [forkPoint.head.id]: []
1160
+ },
1161
+ stateOverrides: forkPoint.stateOverrides,
1162
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.credentials),
1163
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkIssuers),
1164
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkChangedSubjects),
1165
+ state: forkPoint.state.mapValues((v) => v),
1166
+ head: paths[0].head
1167
+ };
1168
+ const subjectToBranch = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1169
+ for (const path of paths) {
1170
+ (0, import_log3.log)("processing a path", () => ({
1171
+ choices: toChosenPath(path),
1172
+ modified: path.forkChangedSubjects,
1173
+ forkIssuers: path.forkIssuers,
1174
+ state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
1175
+ }), {
1176
+ F: __dxlog_file3,
1177
+ L: 348,
1178
+ S: this,
1179
+ C: (f, a) => f(...a)
1180
+ });
1181
+ path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
1182
+ path.credentials.forEach((cred) => result.credentials.add(cred));
1183
+ result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
1184
+ for (const modifiedSubject of path.forkChangedSubjects) {
1185
+ const existingBranch = subjectToBranch.get(modifiedSubject);
1186
+ if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) {
1187
+ subjectToBranch.set(modifiedSubject, path);
1188
+ }
1189
+ }
1190
+ }
1191
+ const replayPaths = /* @__PURE__ */ new Set();
1192
+ const addReplayPath = replayPaths.add.bind(replayPaths);
1193
+ for (const [subject, branch] of subjectToBranch.entries()) {
1194
+ result.forkChangedSubjects.add(subject);
1195
+ const vertex = branch.state.get(subject);
1196
+ result.state.set(subject, vertex);
1197
+ (0, import_log3.log)("set subject state", () => ({
1198
+ subject,
1199
+ state: this._stateHandler.toLogString(vertex.assertion)
1200
+ }), {
1201
+ F: __dxlog_file3,
1202
+ L: 370,
1203
+ S: this,
1204
+ C: (f, a) => f(...a)
1205
+ });
1206
+ const otherPaths = paths.filter((p) => p !== branch);
1207
+ this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
1208
+ }
1209
+ if (replayPaths.size > 0) {
1210
+ return {
1211
+ type: "replay_required",
1212
+ replay: [
1213
+ ...replayPaths.values()
1214
+ ],
1215
+ from: forkPoint,
1216
+ stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
1217
+ };
1218
+ }
1219
+ return {
1220
+ type: "merged",
1221
+ path: result
1222
+ };
1223
+ }
1224
+ /**
1225
+ * A candidate credential is preferred over the existing credential if:
1226
+ * 1. It is the merge-point, because it's the last credential that was issued in awareness of all
1227
+ * the previously existing ones.
1228
+ * 2. A path where candidate was set contains existing credential in it, which means that the candidate
1229
+ * was issued after the existing credential by a legitimate issuer.
1230
+ * 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
1231
+ * 4. The path where candidate was set has more issuers than the existing path (longer branch).
1232
+ * 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
1233
+ */
1234
+ _shouldOverrideCredential(existing, candidate, modifiedSubject) {
1235
+ const candidateVertex = candidate.state.get(modifiedSubject);
1236
+ const currentVertex = existing.state.get(modifiedSubject);
1237
+ if (candidateVertex.id === currentVertex.id) {
1238
+ return false;
1239
+ }
1240
+ const mergePointId = existing.head.id;
1241
+ if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
1242
+ (0, import_log3.log)("merge point chosen to break the tie", {
1243
+ mergePointId: existing.head.id
1244
+ }, {
1245
+ F: __dxlog_file3,
1246
+ L: 408,
1247
+ S: this,
1248
+ C: (f, a) => f(...a)
1249
+ });
1250
+ return mergePointId === candidateVertex.id;
1251
+ }
1252
+ const candidateCredential = candidateVertex.credential;
1253
+ const currentCredential = currentVertex.credential;
1254
+ if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
1255
+ (0, import_log3.log)("one of the credentials was overridden in another branch", {
1256
+ current: currentVertex.id,
1257
+ candidate: candidateVertex.id
1258
+ }, {
1259
+ F: __dxlog_file3,
1260
+ L: 415,
1261
+ S: this,
1262
+ C: (f, a) => f(...a)
1263
+ });
1264
+ return candidate.credentials.has(currentCredential.id);
1265
+ }
1266
+ const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
1267
+ if (winningCredential != null) {
1268
+ return winningCredential === candidateCredential;
1269
+ }
1270
+ if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
1271
+ (0, import_log3.log)("longer issuers branch used to break the tie", {
1272
+ issuerCount: [
1273
+ existing.forkIssuers.size,
1274
+ candidate.forkIssuers.size
1275
+ ]
1276
+ }, {
1277
+ F: __dxlog_file3,
1278
+ L: 432,
1279
+ S: this,
1280
+ C: (f, a) => f(...a)
1281
+ });
1282
+ return candidate.forkIssuers.size > existing.forkIssuers.size;
1283
+ }
1284
+ (0, import_log3.log)("issuance date used to break the tie", void 0, {
1285
+ F: __dxlog_file3,
1286
+ L: 437,
1287
+ S: this,
1288
+ C: (f, a) => f(...a)
1289
+ });
1290
+ return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
1291
+ }
1292
+ _createRootPath() {
1293
+ return {
1294
+ head: this._root,
1295
+ chosenPath: {},
1296
+ forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1297
+ forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
1298
+ state: new import_util6.ComplexMap(import_keys7.PublicKey.hash),
1299
+ credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash)
1300
+ };
1301
+ }
1302
+ };
1303
+ var toChosenPath = (path) => {
1304
+ return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [
1305
+ k,
1306
+ vs.map((v) => v.id)
1307
+ ]));
1308
+ };
1309
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
739
1310
  var MemberStateMachine = class {
740
1311
  constructor(_spaceKey) {
741
1312
  this._spaceKey = _spaceKey;
742
- this._members = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
743
- this.onMemberAdmitted = new import_util5.Callback();
1313
+ this._memberProfiles = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
1314
+ this._hashgraph = new CredentialGraph(this);
1315
+ this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
744
1316
  }
745
1317
  get creator() {
746
- return this._creator;
1318
+ return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
747
1319
  }
748
1320
  get members() {
749
- return this._members;
1321
+ return this._hashgraph.getState();
1322
+ }
1323
+ get membershipChainHeads() {
1324
+ return this._hashgraph.getLeafIds();
750
1325
  }
751
1326
  getRole(member) {
752
- return this._members.get(member)?.assertion.role;
1327
+ return this._getRole(this._hashgraph.getGlobalStateScope(), member);
753
1328
  }
754
1329
  /**
755
1330
  * Processes the SpaceMember credential.
@@ -760,84 +1335,122 @@ var MemberStateMachine = class {
760
1335
  switch (assertion["@type"]) {
761
1336
  case "dxos.halo.credentials.SpaceMember": {
762
1337
  (0, import_invariant3.invariant)(assertion.spaceKey.equals(this._spaceKey), void 0, {
763
- F: __dxlog_file3,
764
- L: 58,
1338
+ F: __dxlog_file4,
1339
+ L: 66,
765
1340
  S: this,
766
1341
  A: [
767
1342
  "assertion.spaceKey.equals(this._spaceKey)",
768
1343
  ""
769
1344
  ]
770
1345
  });
771
- if (this._members.has(credential.subject.id)) {
772
- return;
1346
+ if (this._ownerKey == null && credential.issuer === this._spaceKey) {
1347
+ this._ownerKey = credential.subject.id;
773
1348
  }
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;
1349
+ if (assertion.profile != null) {
1350
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
783
1351
  }
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);
1352
+ await this._hashgraph.addVertex(credential, assertion);
797
1353
  break;
798
1354
  }
799
1355
  case "dxos.halo.credentials.MemberProfile": {
800
- const member = this._members.get(credential.subject.id);
801
- if (!member) {
1356
+ const member = this._hashgraph.getSubjectState(credential.subject.id);
1357
+ if (member) {
1358
+ member.profile = assertion.profile;
1359
+ } else {
802
1360
  import_log2.log.warn("Member not found", {
803
1361
  id: credential.subject.id
804
1362
  }, {
805
- F: __dxlog_file3,
806
- L: 93,
1363
+ F: __dxlog_file4,
1364
+ L: 81,
807
1365
  S: this,
808
1366
  C: (f, a) => f(...a)
809
1367
  });
810
- } else {
811
- member.profile = assertion.profile;
812
1368
  }
1369
+ this._memberProfiles.set(credential.subject.id, assertion.profile);
813
1370
  break;
814
1371
  }
815
1372
  default:
816
1373
  throw new Error("Invalid assertion type");
817
1374
  }
818
1375
  }
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;
1376
+ createState(credential, assertion) {
1377
+ const memberKey = credential.subject.id;
1378
+ return {
1379
+ key: memberKey,
1380
+ role: assertion.role,
1381
+ credential,
1382
+ assertion,
1383
+ profile: this._memberProfiles.get(memberKey)
1384
+ };
1385
+ }
1386
+ isUpdateAllowed(scope, credential, assertion) {
1387
+ if (assertion.role === import_credentials3.SpaceMember.Role.OWNER) {
1388
+ return credential.issuer.equals(this._spaceKey);
1389
+ }
1390
+ const issuer = credential.issuer;
1391
+ const isChangingOwnRole = issuer.equals(credential.subject.id);
1392
+ if (isChangingOwnRole) {
1393
+ return false;
1394
+ }
1395
+ if (issuer.equals(assertion.spaceKey)) {
1396
+ return true;
1397
+ }
1398
+ const issuerRole = this._getRole(scope, issuer);
1399
+ return issuerRole === import_credentials3.SpaceMember.Role.ADMIN || issuerRole === import_credentials3.SpaceMember.Role.OWNER;
1400
+ }
1401
+ getConflictingPaths(paths, update) {
1402
+ if (update.assertion.role !== import_credentials3.SpaceMember.Role.REMOVED && update.assertion.role !== import_credentials3.SpaceMember.Role.EDITOR) {
1403
+ return [];
1404
+ }
1405
+ const memberId = update.credential.subject.id;
1406
+ return paths.filter((p) => p.forkIssuers.has(memberId));
1407
+ }
1408
+ tryPickWinningUpdate(scope1, update1, scope2, update2) {
1409
+ const path1IssuerRole = this._getRole(scope1, update1.issuer);
1410
+ const path2IssuerRole = this._getRole(scope2, update2.issuer);
1411
+ if (path2IssuerRole === import_credentials3.SpaceMember.Role.OWNER !== (path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER)) {
1412
+ (0, import_log2.log)("owner decision used to break the tie", void 0, {
1413
+ F: __dxlog_file4,
1414
+ L: 139,
1415
+ S: this,
1416
+ C: (f, a) => f(...a)
1417
+ });
1418
+ return path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER ? update1 : update2;
1419
+ }
1420
+ return null;
1421
+ }
1422
+ toLogString(assertion) {
1423
+ const role = assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
1424
+ return Object.entries(import_credentials3.SpaceMember.Role).find(([_, value]) => value === role)[0];
1425
+ }
1426
+ hasStateChanged(s1, s2) {
1427
+ return s1?.role !== s2?.role;
1428
+ }
1429
+ _getRole(scope, memberId) {
1430
+ if (this._ownerKey?.equals(memberId)) {
1431
+ return import_credentials3.SpaceMember.Role.OWNER;
1432
+ }
1433
+ const realRole = scope.state.get(memberId)?.assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
1434
+ if (scope.stateOverrides != null) {
1435
+ const override = scope.stateOverrides.get(memberId);
1436
+ if (override != null) {
1437
+ (0, import_log2.log)("member role overridden in path", () => ({
1438
+ headId: scope.head?.id,
1439
+ roleOverride: this.toLogString(override.assertion),
1440
+ realRole: this.toLogString(scope.state.get(memberId)?.assertion)
1441
+ }), {
1442
+ F: __dxlog_file4,
1443
+ L: 162,
1444
+ S: this,
1445
+ C: (f, a) => f(...a)
1446
+ });
1447
+ return override.assertion.role;
833
1448
  }
834
1449
  }
1450
+ return realRole;
835
1451
  }
836
1452
  };
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
- ];
1453
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
841
1454
  var SpaceStateMachine = class {
842
1455
  constructor(_spaceKey) {
843
1456
  this._spaceKey = _spaceKey;
@@ -849,7 +1462,7 @@ var SpaceStateMachine = class {
849
1462
  this._processedCredentials = new import_util2.ComplexSet(import_keys3.PublicKey.hash);
850
1463
  this._credentialProcessors = [];
851
1464
  this.onCredentialProcessed = new import_util2.Callback();
852
- this.onMemberAdmitted = this._members.onMemberAdmitted;
1465
+ this.onMemberRoleChanged = this._members.onMemberRoleChanged;
853
1466
  this.onFeedAdmitted = this._feeds.onFeedAdmitted;
854
1467
  this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
855
1468
  this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
@@ -860,6 +1473,9 @@ var SpaceStateMachine = class {
860
1473
  get members() {
861
1474
  return this._members.members;
862
1475
  }
1476
+ get membershipChainHeads() {
1477
+ return this._members.membershipChainHeads;
1478
+ }
863
1479
  get feeds() {
864
1480
  return this._feeds.feeds;
865
1481
  }
@@ -912,8 +1528,8 @@ var SpaceStateMachine = class {
912
1528
  const result = await verifyCredential(credential);
913
1529
  if (result.kind !== "pass") {
914
1530
  import_log.log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
915
- F: __dxlog_file4,
916
- L: 149,
1531
+ F: __dxlog_file5,
1532
+ L: 152,
917
1533
  S: this,
918
1534
  C: (f, a) => f(...a)
919
1535
  });
@@ -922,43 +1538,11 @@ var SpaceStateMachine = class {
922
1538
  }
923
1539
  const assertion = getCredentialAssertion(credential);
924
1540
  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
1541
  case "dxos.halo.credentials.SpaceGenesis": {
958
1542
  if (this._genesisCredential) {
959
1543
  import_log.log.warn("Space already has a genesis credential.", void 0, {
960
- F: __dxlog_file4,
961
- L: 176,
1544
+ F: __dxlog_file5,
1545
+ L: 161,
962
1546
  S: this,
963
1547
  C: (f, a) => f(...a)
964
1548
  });
@@ -966,8 +1550,8 @@ var SpaceStateMachine = class {
966
1550
  }
967
1551
  if (!credential.issuer.equals(this._spaceKey)) {
968
1552
  import_log.log.warn("Space genesis credential must be issued by space.", void 0, {
969
- F: __dxlog_file4,
970
- L: 180,
1553
+ F: __dxlog_file5,
1554
+ L: 165,
971
1555
  S: this,
972
1556
  C: (f, a) => f(...a)
973
1557
  });
@@ -975,8 +1559,8 @@ var SpaceStateMachine = class {
975
1559
  }
976
1560
  if (!credential.subject.id.equals(this._spaceKey)) {
977
1561
  import_log.log.warn("Space genesis credential must be issued to space.", void 0, {
978
- F: __dxlog_file4,
979
- L: 184,
1562
+ F: __dxlog_file5,
1563
+ L: 169,
980
1564
  S: this,
981
1565
  C: (f, a) => f(...a)
982
1566
  });
@@ -991,8 +1575,8 @@ var SpaceStateMachine = class {
991
1575
  }
992
1576
  if (!this._genesisCredential) {
993
1577
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
994
- F: __dxlog_file4,
995
- L: 198,
1578
+ F: __dxlog_file5,
1579
+ L: 182,
996
1580
  S: this,
997
1581
  C: (f, a) => f(...a)
998
1582
  });
@@ -1000,8 +1584,8 @@ var SpaceStateMachine = class {
1000
1584
  }
1001
1585
  if (!this._canInviteNewMembers(credential.issuer)) {
1002
1586
  import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
1003
- F: __dxlog_file4,
1004
- L: 202,
1587
+ F: __dxlog_file5,
1588
+ L: 186,
1005
1589
  S: this,
1006
1590
  C: (f, a) => f(...a)
1007
1591
  });
@@ -1014,8 +1598,8 @@ var SpaceStateMachine = class {
1014
1598
  case "dxos.halo.credentials.MemberProfile": {
1015
1599
  if (!this._genesisCredential) {
1016
1600
  import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
1017
- F: __dxlog_file4,
1018
- L: 213,
1601
+ F: __dxlog_file5,
1602
+ L: 197,
1019
1603
  S: this,
1020
1604
  C: (f, a) => f(...a)
1021
1605
  });
@@ -1027,8 +1611,8 @@ var SpaceStateMachine = class {
1027
1611
  case "dxos.halo.credentials.AdmittedFeed": {
1028
1612
  if (!this._genesisCredential) {
1029
1613
  import_log.log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
1030
- F: __dxlog_file4,
1031
- L: 223,
1614
+ F: __dxlog_file5,
1615
+ L: 207,
1032
1616
  S: this,
1033
1617
  C: (f, a) => f(...a)
1034
1618
  });
@@ -1036,8 +1620,8 @@ var SpaceStateMachine = class {
1036
1620
  }
1037
1621
  if (!this._canAdmitFeeds(credential.issuer)) {
1038
1622
  import_log.log.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
1039
- F: __dxlog_file4,
1040
- L: 227,
1623
+ F: __dxlog_file5,
1624
+ L: 211,
1041
1625
  S: this,
1042
1626
  C: (f, a) => f(...a)
1043
1627
  });
@@ -1050,8 +1634,8 @@ var SpaceStateMachine = class {
1050
1634
  case "dxos.halo.invitations.DelegateSpaceInvitation": {
1051
1635
  if (!this._canInviteNewMembers(credential.issuer)) {
1052
1636
  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,
1637
+ F: __dxlog_file5,
1638
+ L: 222,
1055
1639
  S: this,
1056
1640
  C: (f, a) => f(...a)
1057
1641
  });
@@ -1079,17 +1663,11 @@ var SpaceStateMachine = class {
1079
1663
  return true;
1080
1664
  }
1081
1665
  _canInviteNewMembers(key) {
1082
- return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
1666
+ return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN || this._members.getRole(key) === import_credentials2.SpaceMember.Role.OWNER;
1083
1667
  }
1084
1668
  _canAdmitFeeds(key) {
1085
1669
  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"]);
1670
+ return role === import_credentials2.SpaceMember.Role.EDITOR || role === import_credentials2.SpaceMember.Role.ADMIN || role === import_credentials2.SpaceMember.Role.OWNER;
1093
1671
  }
1094
1672
  };
1095
1673
  var CredentialConsumer = class {
@@ -1119,20 +1697,20 @@ var CredentialConsumer = class {
1119
1697
  await this._onClose();
1120
1698
  }
1121
1699
  };
1122
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1700
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
1123
1701
  var DeviceStateMachine = class {
1124
1702
  constructor(_params) {
1125
1703
  this._params = _params;
1126
- this.authorizedDeviceKeys = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1704
+ this.authorizedDeviceKeys = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
1127
1705
  this.deviceChainReady = new import_async2.Trigger();
1128
1706
  }
1129
1707
  async processCredential(credential) {
1130
- (0, import_log3.log)("processing credential...", {
1708
+ (0, import_log4.log)("processing credential...", {
1131
1709
  identityKey: this._params.identityKey,
1132
1710
  deviceKey: this._params.deviceKey,
1133
1711
  credential
1134
1712
  }, {
1135
- F: __dxlog_file5,
1713
+ F: __dxlog_file6,
1136
1714
  L: 35,
1137
1715
  S: this,
1138
1716
  C: (f, a) => f(...a)
@@ -1146,8 +1724,8 @@ var DeviceStateMachine = class {
1146
1724
  const assertion = getCredentialAssertion(credential);
1147
1725
  switch (assertion["@type"]) {
1148
1726
  case "dxos.halo.credentials.AuthorizedDevice": {
1149
- (0, import_invariant4.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1150
- F: __dxlog_file5,
1727
+ (0, import_invariant5.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
1728
+ F: __dxlog_file6,
1151
1729
  L: 51,
1152
1730
  S: this,
1153
1731
  A: [
@@ -1156,12 +1734,12 @@ var DeviceStateMachine = class {
1156
1734
  ]
1157
1735
  });
1158
1736
  this.authorizedDeviceKeys.set(assertion.deviceKey, {});
1159
- (0, import_log3.log)("added device", {
1737
+ (0, import_log4.log)("added device", {
1160
1738
  localDeviceKey: this._params.deviceKey,
1161
1739
  deviceKey: assertion.deviceKey,
1162
1740
  size: this.authorizedDeviceKeys.size
1163
1741
  }, {
1164
- F: __dxlog_file5,
1742
+ F: __dxlog_file6,
1165
1743
  L: 54,
1166
1744
  S: this,
1167
1745
  C: (f, a) => f(...a)
@@ -1170,8 +1748,8 @@ var DeviceStateMachine = class {
1170
1748
  break;
1171
1749
  }
1172
1750
  case "dxos.halo.credentials.DeviceProfile": {
1173
- (0, import_invariant4.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1174
- F: __dxlog_file5,
1751
+ (0, import_invariant5.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
1752
+ F: __dxlog_file6,
1175
1753
  L: 64,
1176
1754
  S: this,
1177
1755
  A: [
@@ -1180,11 +1758,11 @@ var DeviceStateMachine = class {
1180
1758
  ]
1181
1759
  });
1182
1760
  if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
1183
- import_log3.log.trace("dxos.halo.device", {
1761
+ import_log4.log.trace("dxos.halo.device", {
1184
1762
  deviceKey: credential.subject.id,
1185
1763
  profile: assertion.profile
1186
1764
  }, {
1187
- F: __dxlog_file5,
1765
+ F: __dxlog_file6,
1188
1766
  L: 67,
1189
1767
  S: this,
1190
1768
  C: (f, a) => f(...a)
@@ -1197,7 +1775,7 @@ var DeviceStateMachine = class {
1197
1775
  }
1198
1776
  }
1199
1777
  };
1200
- var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1778
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
1201
1779
  var ProfileStateMachine = class {
1202
1780
  constructor(_params) {
1203
1781
  this._params = _params;
@@ -1207,11 +1785,11 @@ var ProfileStateMachine = class {
1207
1785
  switch (assertion["@type"]) {
1208
1786
  case "dxos.halo.credentials.IdentityProfile": {
1209
1787
  if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
1210
- import_log4.log.warn("Invalid profile credential", {
1788
+ import_log5.log.warn("Invalid profile credential", {
1211
1789
  expectedIdentity: this._params.identityKey,
1212
1790
  credential
1213
1791
  }, {
1214
- F: __dxlog_file6,
1792
+ F: __dxlog_file7,
1215
1793
  L: 34,
1216
1794
  S: this,
1217
1795
  C: (f, a) => f(...a)
@@ -1219,11 +1797,11 @@ var ProfileStateMachine = class {
1219
1797
  return;
1220
1798
  }
1221
1799
  this.profile = assertion.profile;
1222
- (0, import_log4.log)("updated profile", {
1800
+ (0, import_log5.log)("updated profile", {
1223
1801
  identityKey: this._params.identityKey,
1224
1802
  profile: this.profile
1225
1803
  }, {
1226
- F: __dxlog_file6,
1804
+ F: __dxlog_file7,
1227
1805
  L: 40,
1228
1806
  S: this,
1229
1807
  C: (f, a) => f(...a)