@brightchain/brightchain-lib 0.18.0 → 0.20.0

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 (372) hide show
  1. package/package.json +2 -2
  2. package/src/lib/db/aggregation.d.ts +20 -0
  3. package/src/lib/db/aggregation.d.ts.map +1 -0
  4. package/src/lib/db/aggregation.js +407 -0
  5. package/src/lib/db/aggregation.js.map +1 -0
  6. package/src/lib/db/collection.d.ts +315 -0
  7. package/src/lib/db/collection.d.ts.map +1 -0
  8. package/src/lib/db/collection.js +1054 -0
  9. package/src/lib/db/collection.js.map +1 -0
  10. package/src/lib/db/cursor.d.ts +51 -0
  11. package/src/lib/db/cursor.d.ts.map +1 -0
  12. package/src/lib/db/cursor.js +100 -0
  13. package/src/lib/db/cursor.js.map +1 -0
  14. package/src/lib/db/errors.d.ts +83 -0
  15. package/src/lib/db/errors.d.ts.map +1 -0
  16. package/src/lib/db/errors.js +103 -0
  17. package/src/lib/db/errors.js.map +1 -0
  18. package/src/lib/db/inMemoryDatabase.d.ts +135 -0
  19. package/src/lib/db/inMemoryDatabase.d.ts.map +1 -0
  20. package/src/lib/db/inMemoryDatabase.js +258 -0
  21. package/src/lib/db/inMemoryDatabase.js.map +1 -0
  22. package/src/lib/db/inMemoryHeadRegistry.d.ts +33 -0
  23. package/src/lib/db/inMemoryHeadRegistry.d.ts.map +1 -0
  24. package/src/lib/db/inMemoryHeadRegistry.js +83 -0
  25. package/src/lib/db/inMemoryHeadRegistry.js.map +1 -0
  26. package/src/lib/db/index.d.ts +14 -0
  27. package/src/lib/db/index.d.ts.map +1 -0
  28. package/src/lib/db/index.js +18 -0
  29. package/src/lib/db/index.js.map +1 -0
  30. package/src/lib/db/indexing.d.ts +133 -0
  31. package/src/lib/db/indexing.d.ts.map +1 -0
  32. package/src/lib/db/indexing.js +287 -0
  33. package/src/lib/db/indexing.js.map +1 -0
  34. package/src/lib/db/queryEngine.d.ts +50 -0
  35. package/src/lib/db/queryEngine.d.ts.map +1 -0
  36. package/src/lib/db/queryEngine.js +461 -0
  37. package/src/lib/db/queryEngine.js.map +1 -0
  38. package/src/lib/db/schemaValidation.d.ts +41 -0
  39. package/src/lib/db/schemaValidation.d.ts.map +1 -0
  40. package/src/lib/db/schemaValidation.js +322 -0
  41. package/src/lib/db/schemaValidation.js.map +1 -0
  42. package/src/lib/db/transaction.d.ts +88 -0
  43. package/src/lib/db/transaction.d.ts.map +1 -0
  44. package/src/lib/db/transaction.js +112 -0
  45. package/src/lib/db/transaction.js.map +1 -0
  46. package/src/lib/db/types.d.ts +11 -0
  47. package/src/lib/db/types.d.ts.map +1 -0
  48. package/src/lib/db/types.js +11 -0
  49. package/src/lib/db/types.js.map +1 -0
  50. package/src/lib/db/updateEngine.d.ts +20 -0
  51. package/src/lib/db/updateEngine.d.ts.map +1 -0
  52. package/src/lib/db/updateEngine.js +193 -0
  53. package/src/lib/db/updateEngine.js.map +1 -0
  54. package/src/lib/db/uuidGenerator.d.ts +13 -0
  55. package/src/lib/db/uuidGenerator.d.ts.map +1 -0
  56. package/src/lib/db/uuidGenerator.js +34 -0
  57. package/src/lib/db/uuidGenerator.js.map +1 -0
  58. package/src/lib/documents/member/memberProfileHydration.d.ts.map +1 -1
  59. package/src/lib/documents/member/memberProfileHydration.js +6 -0
  60. package/src/lib/documents/member/memberProfileHydration.js.map +1 -1
  61. package/src/lib/enumerations/brightChainStrings.d.ts +39 -0
  62. package/src/lib/enumerations/brightChainStrings.d.ts.map +1 -1
  63. package/src/lib/enumerations/brightChainStrings.js +47 -0
  64. package/src/lib/enumerations/brightChainStrings.js.map +1 -1
  65. package/src/lib/enumerations/identityValidationErrorType.d.ts +11 -0
  66. package/src/lib/enumerations/identityValidationErrorType.d.ts.map +1 -0
  67. package/src/lib/enumerations/identityValidationErrorType.js +15 -0
  68. package/src/lib/enumerations/identityValidationErrorType.js.map +1 -0
  69. package/src/lib/enumerations/index.d.ts +4 -0
  70. package/src/lib/enumerations/index.d.ts.map +1 -1
  71. package/src/lib/enumerations/index.js +5 -0
  72. package/src/lib/enumerations/index.js.map +1 -1
  73. package/src/lib/enumerations/memberStatusType.d.ts +2 -1
  74. package/src/lib/enumerations/memberStatusType.d.ts.map +1 -1
  75. package/src/lib/enumerations/memberStatusType.js +1 -0
  76. package/src/lib/enumerations/memberStatusType.js.map +1 -1
  77. package/src/lib/enumerations/proposalActionType.d.ts +22 -0
  78. package/src/lib/enumerations/proposalActionType.d.ts.map +1 -0
  79. package/src/lib/enumerations/proposalActionType.js +26 -0
  80. package/src/lib/enumerations/proposalActionType.js.map +1 -0
  81. package/src/lib/enumerations/proposalStatus.d.ts +14 -0
  82. package/src/lib/enumerations/proposalStatus.d.ts.map +1 -0
  83. package/src/lib/enumerations/proposalStatus.js +18 -0
  84. package/src/lib/enumerations/proposalStatus.js.map +1 -0
  85. package/src/lib/enumerations/quorumErrorType.d.ts +30 -1
  86. package/src/lib/enumerations/quorumErrorType.d.ts.map +1 -1
  87. package/src/lib/enumerations/quorumErrorType.js +37 -0
  88. package/src/lib/enumerations/quorumErrorType.js.map +1 -1
  89. package/src/lib/enumerations/quorumOperationalMode.d.ts +16 -0
  90. package/src/lib/enumerations/quorumOperationalMode.d.ts.map +1 -0
  91. package/src/lib/enumerations/quorumOperationalMode.js +20 -0
  92. package/src/lib/enumerations/quorumOperationalMode.js.map +1 -0
  93. package/src/lib/enumerations/sealingErrorType.d.ts +3 -1
  94. package/src/lib/enumerations/sealingErrorType.d.ts.map +1 -1
  95. package/src/lib/enumerations/sealingErrorType.js +2 -0
  96. package/src/lib/enumerations/sealingErrorType.js.map +1 -1
  97. package/src/lib/errors/identityValidationError.d.ts +8 -0
  98. package/src/lib/errors/identityValidationError.d.ts.map +1 -0
  99. package/src/lib/errors/identityValidationError.js +26 -0
  100. package/src/lib/errors/identityValidationError.js.map +1 -0
  101. package/src/lib/errors/index.d.ts +4 -0
  102. package/src/lib/errors/index.d.ts.map +1 -1
  103. package/src/lib/errors/index.js +7 -0
  104. package/src/lib/errors/index.js.map +1 -1
  105. package/src/lib/errors/memberIndexSchemaValidationError.d.ts +6 -0
  106. package/src/lib/errors/memberIndexSchemaValidationError.d.ts.map +1 -0
  107. package/src/lib/errors/memberIndexSchemaValidationError.js +15 -0
  108. package/src/lib/errors/memberIndexSchemaValidationError.js.map +1 -0
  109. package/src/lib/errors/quorumError.d.ts.map +1 -1
  110. package/src/lib/errors/quorumError.js +37 -0
  111. package/src/lib/errors/quorumError.js.map +1 -1
  112. package/src/lib/errors/sealingError.d.ts.map +1 -1
  113. package/src/lib/errors/sealingError.js +2 -0
  114. package/src/lib/errors/sealingError.js.map +1 -1
  115. package/src/lib/i18n/strings/englishUs.d.ts.map +1 -1
  116. package/src/lib/i18n/strings/englishUs.js +45 -0
  117. package/src/lib/i18n/strings/englishUs.js.map +1 -1
  118. package/src/lib/i18n/strings/french.d.ts.map +1 -1
  119. package/src/lib/i18n/strings/french.js +37 -0
  120. package/src/lib/i18n/strings/french.js.map +1 -1
  121. package/src/lib/i18n/strings/german.d.ts.map +1 -1
  122. package/src/lib/i18n/strings/german.js +37 -0
  123. package/src/lib/i18n/strings/german.js.map +1 -1
  124. package/src/lib/i18n/strings/japanese.d.ts.map +1 -1
  125. package/src/lib/i18n/strings/japanese.js +37 -0
  126. package/src/lib/i18n/strings/japanese.js.map +1 -1
  127. package/src/lib/i18n/strings/mandarin.d.ts.map +1 -1
  128. package/src/lib/i18n/strings/mandarin.js +37 -0
  129. package/src/lib/i18n/strings/mandarin.js.map +1 -1
  130. package/src/lib/i18n/strings/spanish.d.ts.map +1 -1
  131. package/src/lib/i18n/strings/spanish.js +37 -0
  132. package/src/lib/i18n/strings/spanish.js.map +1 -1
  133. package/src/lib/i18n/strings/ukrainian.d.ts.map +1 -1
  134. package/src/lib/i18n/strings/ukrainian.js +37 -0
  135. package/src/lib/i18n/strings/ukrainian.js.map +1 -1
  136. package/src/lib/index.d.ts +12 -0
  137. package/src/lib/index.d.ts.map +1 -1
  138. package/src/lib/index.js +55 -1
  139. package/src/lib/index.js.map +1 -1
  140. package/src/lib/interfaces/aliasRecord.d.ts +34 -0
  141. package/src/lib/interfaces/aliasRecord.d.ts.map +1 -0
  142. package/src/lib/interfaces/aliasRecord.js +11 -0
  143. package/src/lib/interfaces/aliasRecord.js.map +1 -0
  144. package/src/lib/interfaces/api/index.d.ts +2 -0
  145. package/src/lib/interfaces/api/index.d.ts.map +1 -0
  146. package/src/lib/interfaces/api/index.js +5 -0
  147. package/src/lib/interfaces/api/index.js.map +1 -0
  148. package/src/lib/interfaces/api/quorumApi.d.ts +97 -0
  149. package/src/lib/interfaces/api/quorumApi.d.ts.map +1 -0
  150. package/src/lib/interfaces/api/quorumApi.js +12 -0
  151. package/src/lib/interfaces/api/quorumApi.js.map +1 -0
  152. package/src/lib/interfaces/auditLogEntry.d.ts +34 -0
  153. package/src/lib/interfaces/auditLogEntry.d.ts.map +1 -0
  154. package/src/lib/interfaces/auditLogEntry.js +10 -0
  155. package/src/lib/interfaces/auditLogEntry.js.map +1 -0
  156. package/src/lib/interfaces/availability/gossipService.d.ts +116 -2
  157. package/src/lib/interfaces/availability/gossipService.d.ts.map +1 -1
  158. package/src/lib/interfaces/availability/gossipService.js +62 -0
  159. package/src/lib/interfaces/availability/gossipService.js.map +1 -1
  160. package/src/lib/interfaces/chainedAuditLogEntry.d.ts +27 -0
  161. package/src/lib/interfaces/chainedAuditLogEntry.d.ts.map +1 -0
  162. package/src/lib/interfaces/chainedAuditLogEntry.js +12 -0
  163. package/src/lib/interfaces/chainedAuditLogEntry.js.map +1 -0
  164. package/src/lib/interfaces/contentWithIdentity.d.ts +39 -0
  165. package/src/lib/interfaces/contentWithIdentity.d.ts.map +1 -0
  166. package/src/lib/interfaces/contentWithIdentity.js +24 -0
  167. package/src/lib/interfaces/contentWithIdentity.js.map +1 -0
  168. package/src/lib/interfaces/energyAccount.d.ts +3 -1
  169. package/src/lib/interfaces/energyAccount.d.ts.map +1 -1
  170. package/src/lib/interfaces/identityRecoveryRecord.d.ts +41 -0
  171. package/src/lib/interfaces/identityRecoveryRecord.d.ts.map +1 -0
  172. package/src/lib/interfaces/identityRecoveryRecord.js +11 -0
  173. package/src/lib/interfaces/identityRecoveryRecord.js.map +1 -0
  174. package/src/lib/interfaces/index.d.ts +17 -0
  175. package/src/lib/interfaces/index.d.ts.map +1 -1
  176. package/src/lib/interfaces/index.js +4 -0
  177. package/src/lib/interfaces/index.js.map +1 -1
  178. package/src/lib/interfaces/initResult.d.ts +6 -6
  179. package/src/lib/interfaces/initResult.d.ts.map +1 -1
  180. package/src/lib/interfaces/member/brightChainBaseInitResult.d.ts +4 -1
  181. package/src/lib/interfaces/member/brightChainBaseInitResult.d.ts.map +1 -1
  182. package/src/lib/interfaces/member/brightChainInitResult.d.ts +1 -1
  183. package/src/lib/interfaces/member/brightChainInitResult.d.ts.map +1 -1
  184. package/src/lib/interfaces/member/memberData.d.ts +3 -0
  185. package/src/lib/interfaces/member/memberData.d.ts.map +1 -1
  186. package/src/lib/interfaces/member/profileStorage.d.ts +5 -0
  187. package/src/lib/interfaces/member/profileStorage.d.ts.map +1 -1
  188. package/src/lib/interfaces/member-init-config.d.ts +20 -0
  189. package/src/lib/interfaces/member-init-config.d.ts.map +1 -0
  190. package/src/lib/interfaces/member-init-config.js +3 -0
  191. package/src/lib/interfaces/member-init-config.js.map +1 -0
  192. package/src/lib/interfaces/operationalState.d.ts +20 -0
  193. package/src/lib/interfaces/operationalState.d.ts.map +1 -0
  194. package/src/lib/interfaces/operationalState.js +10 -0
  195. package/src/lib/interfaces/operationalState.js.map +1 -0
  196. package/src/lib/interfaces/proposal.d.ts +59 -0
  197. package/src/lib/interfaces/proposal.d.ts.map +1 -0
  198. package/src/lib/interfaces/proposal.js +10 -0
  199. package/src/lib/interfaces/proposal.js.map +1 -0
  200. package/src/lib/interfaces/quorumDocumentMetadata.d.ts +20 -0
  201. package/src/lib/interfaces/quorumDocumentMetadata.d.ts.map +1 -0
  202. package/src/lib/interfaces/quorumDocumentMetadata.js +10 -0
  203. package/src/lib/interfaces/quorumDocumentMetadata.js.map +1 -0
  204. package/src/lib/interfaces/quorumEpoch.d.ts +33 -0
  205. package/src/lib/interfaces/quorumEpoch.d.ts.map +1 -0
  206. package/src/lib/interfaces/quorumEpoch.js +11 -0
  207. package/src/lib/interfaces/quorumEpoch.js.map +1 -0
  208. package/src/lib/interfaces/quorumMetrics.d.ts +49 -0
  209. package/src/lib/interfaces/quorumMetrics.d.ts.map +1 -0
  210. package/src/lib/interfaces/quorumMetrics.js +10 -0
  211. package/src/lib/interfaces/quorumMetrics.js.map +1 -0
  212. package/src/lib/interfaces/redistributionJournalEntry.d.ts +25 -0
  213. package/src/lib/interfaces/redistributionJournalEntry.d.ts.map +1 -0
  214. package/src/lib/interfaces/redistributionJournalEntry.js +11 -0
  215. package/src/lib/interfaces/redistributionJournalEntry.js.map +1 -0
  216. package/src/lib/interfaces/responses/backupCodesResponseData.d.ts +3 -5
  217. package/src/lib/interfaces/responses/backupCodesResponseData.d.ts.map +1 -1
  218. package/src/lib/interfaces/responses/challengeResponseData.d.ts +5 -0
  219. package/src/lib/interfaces/responses/challengeResponseData.d.ts.map +1 -1
  220. package/src/lib/interfaces/responses/codeCountResponseData.d.ts +3 -5
  221. package/src/lib/interfaces/responses/codeCountResponseData.d.ts.map +1 -1
  222. package/src/lib/interfaces/responses/index.d.ts +4 -2
  223. package/src/lib/interfaces/responses/index.d.ts.map +1 -1
  224. package/src/lib/interfaces/responses/passwordChangeResponse.d.ts +2 -0
  225. package/src/lib/interfaces/responses/passwordChangeResponse.d.ts.map +1 -0
  226. package/src/lib/interfaces/responses/passwordChangeResponse.js +3 -0
  227. package/src/lib/interfaces/responses/passwordChangeResponse.js.map +1 -0
  228. package/src/lib/interfaces/responses/recoveryResponse.d.ts +2 -0
  229. package/src/lib/interfaces/responses/recoveryResponse.d.ts.map +1 -0
  230. package/src/lib/interfaces/responses/recoveryResponse.js +3 -0
  231. package/src/lib/interfaces/responses/recoveryResponse.js.map +1 -0
  232. package/src/lib/interfaces/responses/registrationResponseData.d.ts +2 -2
  233. package/src/lib/interfaces/responses/registrationResponseData.d.ts.map +1 -1
  234. package/src/lib/interfaces/services/contentIngestion.d.ts +61 -0
  235. package/src/lib/interfaces/services/contentIngestion.d.ts.map +1 -0
  236. package/src/lib/interfaces/services/contentIngestion.js +12 -0
  237. package/src/lib/interfaces/services/contentIngestion.js.map +1 -0
  238. package/src/lib/interfaces/services/expirationScheduler.d.ts +55 -0
  239. package/src/lib/interfaces/services/expirationScheduler.d.ts.map +1 -0
  240. package/src/lib/interfaces/services/expirationScheduler.js +11 -0
  241. package/src/lib/interfaces/services/expirationScheduler.js.map +1 -0
  242. package/src/lib/interfaces/services/identitySealingPipeline.d.ts +56 -0
  243. package/src/lib/interfaces/services/identitySealingPipeline.d.ts.map +1 -0
  244. package/src/lib/interfaces/services/identitySealingPipeline.js +12 -0
  245. package/src/lib/interfaces/services/identitySealingPipeline.js.map +1 -0
  246. package/src/lib/interfaces/services/identityValidator.d.ts +44 -0
  247. package/src/lib/interfaces/services/identityValidator.d.ts.map +1 -0
  248. package/src/lib/interfaces/services/identityValidator.js +11 -0
  249. package/src/lib/interfaces/services/identityValidator.js.map +1 -0
  250. package/src/lib/interfaces/services/index.d.ts +9 -0
  251. package/src/lib/interfaces/services/index.d.ts.map +1 -1
  252. package/src/lib/interfaces/services/membershipProof.d.ts +40 -0
  253. package/src/lib/interfaces/services/membershipProof.d.ts.map +1 -0
  254. package/src/lib/interfaces/services/membershipProof.js +11 -0
  255. package/src/lib/interfaces/services/membershipProof.js.map +1 -0
  256. package/src/lib/interfaces/services/operatorPrompt.d.ts +68 -0
  257. package/src/lib/interfaces/services/operatorPrompt.d.ts.map +1 -0
  258. package/src/lib/interfaces/services/operatorPrompt.js +11 -0
  259. package/src/lib/interfaces/services/operatorPrompt.js.map +1 -0
  260. package/src/lib/interfaces/services/quorumDatabase.d.ts +207 -0
  261. package/src/lib/interfaces/services/quorumDatabase.d.ts.map +1 -0
  262. package/src/lib/interfaces/services/quorumDatabase.js +13 -0
  263. package/src/lib/interfaces/services/quorumDatabase.js.map +1 -0
  264. package/src/lib/interfaces/services/quorumService.d.ts +3 -0
  265. package/src/lib/interfaces/services/quorumService.d.ts.map +1 -1
  266. package/src/lib/interfaces/services/quorumStateMachine.d.ts +128 -0
  267. package/src/lib/interfaces/services/quorumStateMachine.d.ts.map +1 -0
  268. package/src/lib/interfaces/services/quorumStateMachine.js +12 -0
  269. package/src/lib/interfaces/services/quorumStateMachine.js.map +1 -0
  270. package/src/lib/interfaces/services/redistributionConfig.d.ts +20 -0
  271. package/src/lib/interfaces/services/redistributionConfig.d.ts.map +1 -0
  272. package/src/lib/interfaces/services/redistributionConfig.js +10 -0
  273. package/src/lib/interfaces/services/redistributionConfig.js.map +1 -0
  274. package/src/lib/interfaces/statuteConfig.d.ts +22 -0
  275. package/src/lib/interfaces/statuteConfig.d.ts.map +1 -0
  276. package/src/lib/interfaces/statuteConfig.js +18 -0
  277. package/src/lib/interfaces/statuteConfig.js.map +1 -0
  278. package/src/lib/interfaces/storage/documentStore.d.ts +46 -24
  279. package/src/lib/interfaces/storage/documentStore.d.ts.map +1 -1
  280. package/src/lib/interfaces/storage/documentStore.js +6 -2
  281. package/src/lib/interfaces/storage/documentStore.js.map +1 -1
  282. package/src/lib/interfaces/storage/index.d.ts +5 -0
  283. package/src/lib/interfaces/storage/index.d.ts.map +1 -1
  284. package/src/lib/interfaces/storage/index.js.map +1 -1
  285. package/src/lib/interfaces/storage/memberIndexSchema.d.ts +11 -0
  286. package/src/lib/interfaces/storage/memberIndexSchema.d.ts.map +1 -0
  287. package/src/lib/interfaces/storage/memberIndexSchema.js +26 -0
  288. package/src/lib/interfaces/storage/memberIndexSchema.js.map +1 -0
  289. package/src/lib/interfaces/storage/mnemonicSchema.d.ts +10 -0
  290. package/src/lib/interfaces/storage/mnemonicSchema.d.ts.map +1 -0
  291. package/src/lib/interfaces/storage/mnemonicSchema.js +22 -0
  292. package/src/lib/interfaces/storage/mnemonicSchema.js.map +1 -0
  293. package/src/lib/interfaces/storage/roleSchema.d.ts +10 -0
  294. package/src/lib/interfaces/storage/roleSchema.d.ts.map +1 -0
  295. package/src/lib/interfaces/storage/roleSchema.js +45 -0
  296. package/src/lib/interfaces/storage/roleSchema.js.map +1 -0
  297. package/src/lib/interfaces/storage/userRoleSchema.d.ts +10 -0
  298. package/src/lib/interfaces/storage/userRoleSchema.d.ts.map +1 -0
  299. package/src/lib/interfaces/storage/userRoleSchema.js +35 -0
  300. package/src/lib/interfaces/storage/userRoleSchema.js.map +1 -0
  301. package/src/lib/interfaces/storage/userSchema.d.ts +12 -0
  302. package/src/lib/interfaces/storage/userSchema.d.ts.map +1 -0
  303. package/src/lib/interfaces/storage/userSchema.js +62 -0
  304. package/src/lib/interfaces/storage/userSchema.js.map +1 -0
  305. package/src/lib/interfaces/userManagement.d.ts +49 -0
  306. package/src/lib/interfaces/userManagement.d.ts.map +1 -0
  307. package/src/lib/interfaces/userManagement.js +9 -0
  308. package/src/lib/interfaces/userManagement.js.map +1 -0
  309. package/src/lib/interfaces/vote.d.ts +45 -0
  310. package/src/lib/interfaces/vote.d.ts.map +1 -0
  311. package/src/lib/interfaces/vote.js +10 -0
  312. package/src/lib/interfaces/vote.js.map +1 -0
  313. package/src/lib/quorumDataRecord.d.ts +7 -1
  314. package/src/lib/quorumDataRecord.d.ts.map +1 -1
  315. package/src/lib/quorumDataRecord.js +12 -4
  316. package/src/lib/quorumDataRecord.js.map +1 -1
  317. package/src/lib/quorumDataRecordDto.d.ts +6 -0
  318. package/src/lib/quorumDataRecordDto.d.ts.map +1 -1
  319. package/src/lib/services/aliasRegistry.d.ts +77 -0
  320. package/src/lib/services/aliasRegistry.d.ts.map +1 -0
  321. package/src/lib/services/aliasRegistry.js +138 -0
  322. package/src/lib/services/aliasRegistry.js.map +1 -0
  323. package/src/lib/services/auditLogService.d.ts +100 -0
  324. package/src/lib/services/auditLogService.d.ts.map +1 -0
  325. package/src/lib/services/auditLogService.js +223 -0
  326. package/src/lib/services/auditLogService.js.map +1 -0
  327. package/src/lib/services/blockService.d.ts +2 -1
  328. package/src/lib/services/blockService.d.ts.map +1 -1
  329. package/src/lib/services/blockService.js +7 -2
  330. package/src/lib/services/blockService.js.map +1 -1
  331. package/src/lib/services/identitySealingPipeline.d.ts +120 -0
  332. package/src/lib/services/identitySealingPipeline.d.ts.map +1 -0
  333. package/src/lib/services/identitySealingPipeline.js +288 -0
  334. package/src/lib/services/identitySealingPipeline.js.map +1 -0
  335. package/src/lib/services/identityValidator.d.ts +75 -0
  336. package/src/lib/services/identityValidator.d.ts.map +1 -0
  337. package/src/lib/services/identityValidator.js +202 -0
  338. package/src/lib/services/identityValidator.js.map +1 -0
  339. package/src/lib/services/index.d.ts +6 -0
  340. package/src/lib/services/index.d.ts.map +1 -1
  341. package/src/lib/services/index.js +6 -0
  342. package/src/lib/services/index.js.map +1 -1
  343. package/src/lib/services/member/memberCblService.d.ts.map +1 -1
  344. package/src/lib/services/member/memberCblService.js +12 -1
  345. package/src/lib/services/member/memberCblService.js.map +1 -1
  346. package/src/lib/services/memberStore.d.ts.map +1 -1
  347. package/src/lib/services/memberStore.js +2 -0
  348. package/src/lib/services/memberStore.js.map +1 -1
  349. package/src/lib/services/membershipProofService.d.ts +90 -0
  350. package/src/lib/services/membershipProofService.d.ts.map +1 -0
  351. package/src/lib/services/membershipProofService.js +361 -0
  352. package/src/lib/services/membershipProofService.js.map +1 -0
  353. package/src/lib/services/quorumStateMachine.d.ts +336 -0
  354. package/src/lib/services/quorumStateMachine.d.ts.map +1 -0
  355. package/src/lib/services/quorumStateMachine.js +1396 -0
  356. package/src/lib/services/quorumStateMachine.js.map +1 -0
  357. package/src/lib/services/sealing.service.d.ts +80 -0
  358. package/src/lib/services/sealing.service.d.ts.map +1 -1
  359. package/src/lib/services/sealing.service.js +192 -0
  360. package/src/lib/services/sealing.service.js.map +1 -1
  361. package/src/lib/stores/energyAccountStore.d.ts +13 -11
  362. package/src/lib/stores/energyAccountStore.d.ts.map +1 -1
  363. package/src/lib/stores/energyAccountStore.js +18 -20
  364. package/src/lib/stores/energyAccountStore.js.map +1 -1
  365. /package/{BLOCK_COVERAGE_AUDIT.md → brightchain-lib/BLOCK_COVERAGE_AUDIT.md} +0 -0
  366. /package/{BROWSER_COMPAT.md → brightchain-lib/BROWSER_COMPAT.md} +0 -0
  367. /package/{DEPRECATIONS.md → brightchain-lib/DEPRECATIONS.md} +0 -0
  368. /package/{DEPRECATIONS_REMOVED.md → brightchain-lib/DEPRECATIONS_REMOVED.md} +0 -0
  369. /package/{MIGRATION.md → brightchain-lib/MIGRATION.md} +0 -0
  370. /package/{NAMING_AUDIT.md → brightchain-lib/NAMING_AUDIT.md} +0 -0
  371. /package/{NAMING_CONVENTIONS.md → brightchain-lib/NAMING_CONVENTIONS.md} +0 -0
  372. /package/{README.md → brightchain-lib/README.md} +0 -0
@@ -0,0 +1,1396 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview QuorumStateMachine implementation.
4
+ *
5
+ * Central coordinator for the quorum system. Manages operational mode,
6
+ * epoch lifecycle, proposal/vote orchestration, and delegates to
7
+ * SealingService for cryptographic operations.
8
+ *
9
+ * @see Requirements 1-8, 10-13
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.QuorumStateMachine = void 0;
13
+ const ecies_lib_1 = require("@digitaldefiance/ecies-lib");
14
+ const uuid_1 = require("uuid");
15
+ const proposalActionType_1 = require("../enumerations/proposalActionType");
16
+ const proposalStatus_1 = require("../enumerations/proposalStatus");
17
+ const quorumErrorType_1 = require("../enumerations/quorumErrorType");
18
+ const quorumOperationalMode_1 = require("../enumerations/quorumOperationalMode");
19
+ const quorumError_1 = require("../errors/quorumError");
20
+ const quorumDataRecord_1 = require("../quorumDataRecord");
21
+ const checksum_service_1 = require("./checksum.service");
22
+ /**
23
+ * Constant-time comparison of two Uint8Array buffers.
24
+ * Prevents timing side-channel attacks by always comparing all bytes.
25
+ */
26
+ function constantTimeEqual(a, b) {
27
+ if (a.length !== b.length) {
28
+ return false;
29
+ }
30
+ let result = 0;
31
+ for (let i = 0; i < a.length; i++) {
32
+ result |= a[i] ^ b[i];
33
+ }
34
+ return result === 0;
35
+ }
36
+ /**
37
+ * QuorumStateMachine is the central coordinator for the quorum system.
38
+ *
39
+ * It manages:
40
+ * - Operational mode (Bootstrap / Quorum / TransitionInProgress)
41
+ * - Epoch lifecycle
42
+ * - Document sealing/unsealing with mode-aware delegation
43
+ * - Metrics collection
44
+ *
45
+ * @template TID - Platform ID type for frontend/backend DTO compatibility
46
+ */
47
+ class QuorumStateMachine {
48
+ constructor(db, sealingService, gossipService, auditLogService, aliasRegistry) {
49
+ this.db = db;
50
+ this.sealingService = sealingService;
51
+ this.gossipService = gossipService;
52
+ this.auditLogService = auditLogService;
53
+ this.aliasRegistry = aliasRegistry;
54
+ this.mode = null;
55
+ this.currentEpochData = null;
56
+ this.configuredThreshold = 0;
57
+ this.initialized = false;
58
+ this.checksumService = new checksum_service_1.ChecksumService();
59
+ // Metrics tracking
60
+ this.metricsData = {
61
+ proposalsTotal: 0,
62
+ proposalsPending: 0,
63
+ votesLatencyMs: 0,
64
+ redistributionProgress: -1,
65
+ redistributionFailures: 0,
66
+ expirationLastRun: null,
67
+ expirationDeletedTotal: 0,
68
+ };
69
+ }
70
+ /**
71
+ * Initialize the quorum system.
72
+ *
73
+ * - Checks for persisted operational state first (restore on restart).
74
+ * - If TransitionInProgress is detected, triggers rollback recovery.
75
+ * - Otherwise, determines mode based on member count vs threshold.
76
+ * - Persists the operational state and creates epoch 1.
77
+ */
78
+ async initialize(members, threshold) {
79
+ this.configuredThreshold = threshold;
80
+ // Check for persisted state (restart recovery)
81
+ const persistedState = await this.db.getOperationalState();
82
+ if (persistedState) {
83
+ // Crash recovery: detect TransitionInProgress and rollback
84
+ if (persistedState.mode === quorumOperationalMode_1.QuorumOperationalMode.TransitionInProgress) {
85
+ // Get journal entries for the current epoch to restore documents
86
+ const journalEntries = await this.db.getJournalEntries(persistedState.currentEpochNumber);
87
+ // Roll back already-re-split documents from journal
88
+ for (const entry of journalEntries) {
89
+ const doc = await this.db.getDocument(entry.documentId);
90
+ if (!doc) {
91
+ continue;
92
+ }
93
+ // Restore the document with old shares/members/threshold
94
+ const restoredDoc = new quorumDataRecord_1.QuorumDataRecord(doc.creator, entry.oldMemberIds.map((id) => doc.enhancedProvider.fromBytes(new Uint8Array(Buffer.from(id, 'hex')))), entry.oldThreshold, doc.encryptedData, entry.oldShares, doc.enhancedProvider, doc.checksum, doc.signature, doc.id, doc.dateCreated, new Date(), undefined, entry.oldThreshold < 2, entry.oldEpoch, doc.sealedUnderBootstrap, doc.identityRecoveryRecordId);
95
+ await this.db.saveDocument(restoredDoc);
96
+ }
97
+ // Delete journal entries and reset to Bootstrap
98
+ if (journalEntries.length > 0) {
99
+ await this.db.deleteJournalEntries(persistedState.currentEpochNumber);
100
+ }
101
+ // Reset operational state to Bootstrap
102
+ const bootstrapState = {
103
+ mode: quorumOperationalMode_1.QuorumOperationalMode.Bootstrap,
104
+ currentEpochNumber: persistedState.currentEpochNumber,
105
+ lastUpdated: new Date(),
106
+ };
107
+ await this.db.saveOperationalState(bootstrapState);
108
+ }
109
+ // Restore from persisted state
110
+ const restoredEpoch = await this.db.getEpoch(persistedState.currentEpochNumber);
111
+ if (restoredEpoch) {
112
+ this.mode =
113
+ persistedState.mode === quorumOperationalMode_1.QuorumOperationalMode.TransitionInProgress
114
+ ? quorumOperationalMode_1.QuorumOperationalMode.Bootstrap
115
+ : persistedState.mode;
116
+ this.currentEpochData = restoredEpoch;
117
+ this.initialized = true;
118
+ return restoredEpoch;
119
+ }
120
+ }
121
+ // Fresh initialization: determine mode based on member count vs threshold
122
+ const memberCount = members.length;
123
+ const isBootstrap = memberCount < threshold;
124
+ this.mode = isBootstrap
125
+ ? quorumOperationalMode_1.QuorumOperationalMode.Bootstrap
126
+ : quorumOperationalMode_1.QuorumOperationalMode.Quorum;
127
+ const effectiveThreshold = isBootstrap
128
+ ? Math.max(memberCount, 1)
129
+ : threshold;
130
+ // Save members to database
131
+ for (const member of members) {
132
+ const hexId = (0, ecies_lib_1.uint8ArrayToHex)(member.idBytes);
133
+ const quorumMember = {
134
+ id: hexId,
135
+ publicKey: member.publicKey,
136
+ metadata: { name: `Member-${hexId.substring(0, 8)}` },
137
+ isActive: true,
138
+ createdAt: new Date(),
139
+ updatedAt: new Date(),
140
+ };
141
+ await this.db.saveMember(quorumMember);
142
+ }
143
+ // Create epoch 1
144
+ const memberIds = members.map((m) => (0, ecies_lib_1.uint8ArrayToHex)(m.idBytes));
145
+ const epoch = {
146
+ epochNumber: 1,
147
+ memberIds,
148
+ threshold: effectiveThreshold,
149
+ mode: this.mode,
150
+ createdAt: new Date(),
151
+ };
152
+ await this.db.saveEpoch(epoch);
153
+ // Persist operational state
154
+ const opState = {
155
+ mode: this.mode,
156
+ currentEpochNumber: 1,
157
+ lastUpdated: new Date(),
158
+ };
159
+ await this.db.saveOperationalState(opState);
160
+ this.currentEpochData = epoch;
161
+ this.initialized = true;
162
+ return epoch;
163
+ }
164
+ /**
165
+ * Rollback a transition that was interrupted (crash recovery or failure).
166
+ *
167
+ * For each journal entry, restores the document's old shares, member IDs,
168
+ * and threshold from the journal. Then deletes all journal entries and
169
+ * resets operational state to Bootstrap mode.
170
+ *
171
+ * @param epochNumber - The epoch number to rollback
172
+ * @param emitAudit - Whether to emit a transition_ceremony_failed audit entry (false during crash recovery in initialize)
173
+ */
174
+ async rollbackTransition(epochNumber, emitAudit = false) {
175
+ const journalEntries = await this.db.getJournalEntries(epochNumber);
176
+ // Restore each document from its journal entry
177
+ for (const entry of journalEntries) {
178
+ const doc = await this.db.getDocument(entry.documentId);
179
+ if (!doc) {
180
+ continue;
181
+ }
182
+ // Reconstruct the document with old shares/members/threshold
183
+ const restoredDoc = new quorumDataRecord_1.QuorumDataRecord(doc.creator, entry.oldMemberIds.map((id) => doc.enhancedProvider.fromBytes(new Uint8Array(Buffer.from(id, 'hex')))), entry.oldThreshold, doc.encryptedData, entry.oldShares, doc.enhancedProvider, doc.checksum, doc.signature, doc.id, doc.dateCreated, new Date(), undefined, entry.oldThreshold < 2, // bootstrapMode if threshold < 2
184
+ entry.oldEpoch, doc.sealedUnderBootstrap, doc.identityRecoveryRecordId);
185
+ await this.db.saveDocument(restoredDoc);
186
+ }
187
+ // Delete all journal entries for this epoch
188
+ if (journalEntries.length > 0) {
189
+ await this.db.deleteJournalEntries(epochNumber);
190
+ }
191
+ // Reset operational state to Bootstrap
192
+ const opState = {
193
+ mode: quorumOperationalMode_1.QuorumOperationalMode.Bootstrap,
194
+ currentEpochNumber: epochNumber,
195
+ lastUpdated: new Date(),
196
+ };
197
+ await this.db.saveOperationalState(opState);
198
+ if (emitAudit) {
199
+ await this.emitAuditEntry('transition_ceremony_failed', {
200
+ epochNumber,
201
+ rolledBackDocuments: journalEntries.length,
202
+ });
203
+ }
204
+ }
205
+ /** Ensure the state machine is initialized before operations. */
206
+ ensureInitialized() {
207
+ if (!this.initialized) {
208
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.Uninitialized);
209
+ }
210
+ }
211
+ /** Ensure operations are not blocked by a transition ceremony. */
212
+ ensureNotTransitioning() {
213
+ if (this.mode === quorumOperationalMode_1.QuorumOperationalMode.TransitionInProgress) {
214
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.TransitionInProgress);
215
+ }
216
+ }
217
+ /**
218
+ * Create the next epoch with incremented epoch number.
219
+ *
220
+ * 1. Gets the current epoch number
221
+ * 2. Creates a new epoch with epochNumber = current + 1
222
+ * 3. Saves the epoch to the database
223
+ * 4. Updates the operational state with the new epoch number
224
+ * 5. Updates the cached currentEpochData
225
+ * 6. Returns the new epoch
226
+ *
227
+ * @param memberIds - Active member IDs for the new epoch
228
+ * @param threshold - Threshold for the new epoch
229
+ * @param mode - Operational mode for the new epoch
230
+ * @param previousEpochNumber - Optional override for the previous epoch number
231
+ * @returns The newly created QuorumEpoch
232
+ */
233
+ async createNextEpoch(memberIds, threshold, mode, previousEpochNumber) {
234
+ const currentEpochNum = previousEpochNumber ?? this.currentEpochData?.epochNumber ?? 0;
235
+ const nextEpochNumber = currentEpochNum + 1;
236
+ const epoch = {
237
+ epochNumber: nextEpochNumber,
238
+ memberIds,
239
+ threshold,
240
+ mode,
241
+ createdAt: new Date(),
242
+ previousEpochNumber: currentEpochNum > 0 ? currentEpochNum : undefined,
243
+ };
244
+ await this.db.saveEpoch(epoch);
245
+ // Update operational state
246
+ const opState = {
247
+ mode,
248
+ currentEpochNumber: nextEpochNumber,
249
+ lastUpdated: new Date(),
250
+ };
251
+ await this.db.saveOperationalState(opState);
252
+ // Update cached state
253
+ this.currentEpochData = epoch;
254
+ this.mode = mode;
255
+ return epoch;
256
+ }
257
+ /**
258
+ * Emit an audit log entry.
259
+ *
260
+ * If an AuditLogService is configured, routes through it for chain linking
261
+ * and signing. Otherwise, writes directly to the database.
262
+ *
263
+ * @param eventType - The type of audit event
264
+ * @param details - Additional event-specific details
265
+ * @param targetMemberId - Optional target member ID
266
+ */
267
+ async emitAuditEntry(eventType, details, targetMemberId) {
268
+ const entry = {
269
+ id: (0, uuid_1.v4)(),
270
+ eventType,
271
+ targetMemberId,
272
+ details,
273
+ timestamp: new Date(),
274
+ };
275
+ if (this.auditLogService) {
276
+ await this.auditLogService.appendEntry(entry);
277
+ }
278
+ else {
279
+ await this.db.appendAuditEntry(entry);
280
+ }
281
+ }
282
+ /**
283
+ * Resolve IQuorumMember records into Member objects suitable for SealingService.
284
+ *
285
+ * Creates Member instances with only public keys (no private keys) from
286
+ * the database member records. These are sufficient for encrypting new shares.
287
+ *
288
+ * @param quorumMembers - Database member records to resolve
289
+ * @returns Array of Member objects with public keys
290
+ */
291
+ resolveMembersFromRecords(quorumMembers) {
292
+ return quorumMembers.map((qm) => {
293
+ return new ecies_lib_1.Member(this.sealingService.eciesServiceRef, ecies_lib_1.MemberType.User, qm.metadata.name || `Member-${qm.id.substring(0, 8)}`, new ecies_lib_1.EmailString(`${qm.id.substring(0, 8)}@quorum.local`), qm.publicKey, undefined, // no private key
294
+ undefined, // no wallet
295
+ this.sealingService.enhancedProviderRef.fromBytes(new Uint8Array(Buffer.from(qm.id, 'hex'))));
296
+ });
297
+ }
298
+ /**
299
+ * Perform batched share redistribution for all documents in a given epoch.
300
+ *
301
+ * Processes documents in pages using `listDocumentsByEpoch`, decrypts shares
302
+ * from threshold members, calls `redistributeShares`, updates documents,
303
+ * and saves them back to the database.
304
+ *
305
+ * @param fromEpochNumber - The epoch whose documents need redistribution
306
+ * @param thresholdMembers - Members with private keys to decrypt existing shares
307
+ * @param newMembers - The new member set for re-encryption
308
+ * @param newThreshold - The new threshold for shares
309
+ * @param oldSharingConfig - The old sharing configuration (totalShares, threshold)
310
+ * @param config - Redistribution configuration (batch size, progress, continue-on-failure)
311
+ * @returns Array of failed document IDs
312
+ */
313
+ async redistributeDocuments(fromEpochNumber, thresholdMembers, newMembers, newThreshold, oldSharingConfig, config) {
314
+ const failedDocIds = [];
315
+ let page = 0;
316
+ let processed = 0;
317
+ let totalEstimate = 0;
318
+ const pageSize = config.batchSize;
319
+ // Process documents page by page
320
+ let docs;
321
+ do {
322
+ docs = await this.db.listDocumentsByEpoch(fromEpochNumber, page, pageSize);
323
+ if (page === 0 && docs.length === pageSize) {
324
+ // Rough estimate: at least one more page
325
+ totalEstimate = pageSize * 2;
326
+ }
327
+ else if (page === 0) {
328
+ totalEstimate = docs.length;
329
+ }
330
+ for (const doc of docs) {
331
+ const docHexId = (0, ecies_lib_1.uint8ArrayToHex)(doc.enhancedProvider.toBytes(doc.id));
332
+ try {
333
+ // Decrypt shares from threshold members
334
+ const decryptedShares = await this.sealingService.decryptShares(doc, thresholdMembers);
335
+ // Build a map of memberId -> decrypted share hex
336
+ const sharesMap = new Map();
337
+ for (let i = 0; i < thresholdMembers.length; i++) {
338
+ const memberId = (0, ecies_lib_1.uint8ArrayToHex)(thresholdMembers[i].idBytes);
339
+ sharesMap.set(memberId, decryptedShares[i]);
340
+ }
341
+ // Redistribute shares to new members
342
+ const newEncryptedShares = await this.sealingService.redistributeShares(sharesMap, newMembers, newThreshold, oldSharingConfig);
343
+ // Create updated document with new shares, memberIDs, and threshold
344
+ const newMemberTIDs = newMembers.map((m) => m.id);
345
+ const updatedDoc = new quorumDataRecord_1.QuorumDataRecord(doc.creator, newMemberTIDs, newThreshold, doc.encryptedData, newEncryptedShares, doc.enhancedProvider, doc.checksum, doc.signature, doc.id, doc.dateCreated, new Date(), undefined, newThreshold < 2, // bootstrapMode if threshold < 2
346
+ this.currentEpochData?.epochNumber ?? doc.epochNumber, doc.sealedUnderBootstrap, doc.identityRecoveryRecordId);
347
+ await this.db.saveDocument(updatedDoc);
348
+ processed++;
349
+ }
350
+ catch {
351
+ failedDocIds.push(docHexId);
352
+ if (!config.continueOnFailure) {
353
+ this.metricsData.redistributionFailures += failedDocIds.length;
354
+ return failedDocIds;
355
+ }
356
+ }
357
+ // Report progress
358
+ if (config.onProgress) {
359
+ config.onProgress(processed, totalEstimate, failedDocIds);
360
+ }
361
+ }
362
+ page++;
363
+ } while (docs.length === pageSize);
364
+ // Update metrics
365
+ this.metricsData.redistributionProgress =
366
+ totalEstimate > 0 ? processed / totalEstimate : 1;
367
+ this.metricsData.redistributionFailures += failedDocIds.length;
368
+ return failedDocIds;
369
+ }
370
+ /**
371
+ * Get the current operational mode.
372
+ * Restores from persisted state on first call if needed.
373
+ */
374
+ async getMode() {
375
+ if (this.mode !== null) {
376
+ return this.mode;
377
+ }
378
+ // Try to restore from persisted state
379
+ const persistedState = await this.db.getOperationalState();
380
+ if (persistedState) {
381
+ this.mode = persistedState.mode;
382
+ return this.mode;
383
+ }
384
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.Uninitialized);
385
+ }
386
+ /**
387
+ * Initiate a transition ceremony from Bootstrap to Quorum mode.
388
+ *
389
+ * Flow:
390
+ * 1. Verify members >= threshold, set mode to TransitionInProgress
391
+ * 2. Emit transition_ceremony_started audit entry
392
+ * 3. Block seal/unseal/submitProposal (via ensureNotTransitioning)
393
+ * 4. For each bootstrap-sealed document (paginated):
394
+ * a. Save journal entry with old shares/members/threshold/epoch
395
+ * b. Redistribute shares to full member set with configured threshold
396
+ * c. Update document in database
397
+ * 5. On success: create new epoch in Quorum mode, delete journal entries,
398
+ * emit transition_ceremony_completed, unblock
399
+ * 6. On failure: rollback from journal, reset to Bootstrap, delete journal
400
+ * entries, emit transition_ceremony_failed
401
+ */
402
+ async initiateTransition() {
403
+ this.ensureInitialized();
404
+ if (this.mode !== quorumOperationalMode_1.QuorumOperationalMode.Bootstrap) {
405
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.InvalidModeTransition);
406
+ }
407
+ const activeMembers = await this.db.listActiveMembers();
408
+ if (activeMembers.length < this.configuredThreshold) {
409
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.InsufficientMembersForTransition);
410
+ }
411
+ const currentEpochNumber = this.currentEpochData?.epochNumber ?? 1;
412
+ const previousThreshold = this.currentEpochData?.threshold ?? 1;
413
+ const previousMemberIds = this.currentEpochData?.memberIds ?? [];
414
+ // Set mode to TransitionInProgress — blocks seal/unseal/submitProposal
415
+ this.mode = quorumOperationalMode_1.QuorumOperationalMode.TransitionInProgress;
416
+ const opState = {
417
+ mode: quorumOperationalMode_1.QuorumOperationalMode.TransitionInProgress,
418
+ currentEpochNumber,
419
+ lastUpdated: new Date(),
420
+ };
421
+ await this.db.saveOperationalState(opState);
422
+ // Emit transition_ceremony_started audit entry
423
+ await this.emitAuditEntry('transition_ceremony_started', {
424
+ epochNumber: currentEpochNumber,
425
+ memberCount: activeMembers.length,
426
+ configuredThreshold: this.configuredThreshold,
427
+ });
428
+ // Resolve members for redistribution
429
+ const newMemberObjects = this.resolveMembersFromRecords(activeMembers);
430
+ const newMemberIds = activeMembers.map((m) => m.id);
431
+ // Batched re-split of all bootstrap-sealed documents
432
+ const batchSize = 100;
433
+ let page = 0;
434
+ let docs;
435
+ const failedDocIds = [];
436
+ try {
437
+ // Emit share_redistribution_started
438
+ await this.emitAuditEntry('share_redistribution_started', {
439
+ reason: 'transition_ceremony',
440
+ fromEpoch: currentEpochNumber,
441
+ });
442
+ do {
443
+ docs = await this.db.listDocumentsByEpoch(currentEpochNumber, page, batchSize);
444
+ for (const doc of docs) {
445
+ const docHexId = (0, ecies_lib_1.uint8ArrayToHex)(doc.enhancedProvider.toBytes(doc.id));
446
+ // Save journal entry BEFORE modifying the document
447
+ const oldMemberHexIds = doc.memberIDs.map((mid) => (0, ecies_lib_1.uint8ArrayToHex)(doc.enhancedProvider.toBytes(mid)));
448
+ const journalEntry = {
449
+ documentId: docHexId,
450
+ oldShares: new Map(doc.encryptedSharesByMemberId),
451
+ oldMemberIds: oldMemberHexIds,
452
+ oldThreshold: doc.sharesRequired,
453
+ oldEpoch: doc.epochNumber,
454
+ };
455
+ await this.db.saveJournalEntry(journalEntry);
456
+ try {
457
+ // Decrypt shares from threshold members
458
+ const decryptedShares = await this.sealingService.decryptShares(doc, newMemberObjects);
459
+ // Build shares map
460
+ const sharesMap = new Map();
461
+ for (let i = 0; i < newMemberObjects.length; i++) {
462
+ const memberId = (0, ecies_lib_1.uint8ArrayToHex)(newMemberObjects[i].idBytes);
463
+ sharesMap.set(memberId, decryptedShares[i]);
464
+ }
465
+ // Redistribute shares to full member set with configured threshold
466
+ const newEncryptedShares = await this.sealingService.redistributeShares(sharesMap, newMemberObjects, this.configuredThreshold, {
467
+ totalShares: previousMemberIds.length,
468
+ threshold: previousThreshold,
469
+ });
470
+ // Create updated document with new shares
471
+ const newMemberTIDs = newMemberObjects.map((m) => m.id);
472
+ const updatedDoc = new quorumDataRecord_1.QuorumDataRecord(doc.creator, newMemberTIDs, this.configuredThreshold, doc.encryptedData, newEncryptedShares, doc.enhancedProvider, doc.checksum, doc.signature, doc.id, doc.dateCreated, new Date(), undefined, false, // no longer bootstrap mode
473
+ currentEpochNumber + 1, doc.sealedUnderBootstrap, doc.identityRecoveryRecordId);
474
+ await this.db.saveDocument(updatedDoc);
475
+ }
476
+ catch {
477
+ failedDocIds.push(docHexId);
478
+ // On any failure, abort and rollback
479
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.RedistributionFailed);
480
+ }
481
+ }
482
+ page++;
483
+ } while (docs.length === batchSize);
484
+ // Emit share_redistribution_completed
485
+ await this.emitAuditEntry('share_redistribution_completed', {
486
+ reason: 'transition_ceremony',
487
+ fromEpoch: currentEpochNumber,
488
+ });
489
+ // SUCCESS PATH: all documents re-split
490
+ // Create new epoch in Quorum mode
491
+ await this.createTransitionEpoch(newMemberIds, this.configuredThreshold);
492
+ // Delete journal entries (no longer needed)
493
+ await this.db.deleteJournalEntries(currentEpochNumber);
494
+ // Emit transition_ceremony_completed
495
+ await this.emitAuditEntry('transition_ceremony_completed', {
496
+ epochNumber: this.currentEpochData?.epochNumber,
497
+ previousEpochNumber: currentEpochNumber,
498
+ memberCount: newMemberIds.length,
499
+ threshold: this.configuredThreshold,
500
+ });
501
+ }
502
+ catch (error) {
503
+ // FAILURE PATH: rollback from journal
504
+ await this.rollbackTransition(currentEpochNumber, true);
505
+ // Restore in-memory state to Bootstrap
506
+ this.mode = quorumOperationalMode_1.QuorumOperationalMode.Bootstrap;
507
+ // Emit share_redistribution_failed if we got past the start
508
+ if (failedDocIds.length > 0) {
509
+ await this.emitAuditEntry('share_redistribution_failed', {
510
+ reason: 'transition_ceremony',
511
+ failedDocumentIds: failedDocIds,
512
+ failedCount: failedDocIds.length,
513
+ });
514
+ }
515
+ // Re-throw the error so the caller knows the ceremony failed
516
+ if (error instanceof quorumError_1.QuorumError) {
517
+ throw error;
518
+ }
519
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.RedistributionFailed);
520
+ }
521
+ }
522
+ /**
523
+ * Create a new epoch for a completed transition ceremony.
524
+ *
525
+ * Called by the transition ceremony logic (Task 10) when all documents
526
+ * have been successfully re-split. Creates a new epoch in Quorum mode
527
+ * with the full member set and configured threshold.
528
+ *
529
+ * @param memberIds - The full member set after transition
530
+ * @param threshold - The configured quorum threshold
531
+ * @returns The new QuorumEpoch in Quorum mode
532
+ */
533
+ async createTransitionEpoch(memberIds, threshold) {
534
+ return this.createNextEpoch(memberIds, threshold, quorumOperationalMode_1.QuorumOperationalMode.Quorum);
535
+ }
536
+ /**
537
+ * Add a new member to the quorum.
538
+ *
539
+ * Saves the member, increments the epoch, triggers batched share
540
+ * redistribution for all documents in the previous epoch, and emits
541
+ * audit entries for member_added and share_redistribution_*.
542
+ *
543
+ * @param member - The member to add
544
+ * @param metadata - Metadata for the member
545
+ * @param redistributionConfig - Optional redistribution configuration
546
+ * @returns The new QuorumEpoch after member addition
547
+ */
548
+ async addMember(member, metadata, redistributionConfig) {
549
+ this.ensureInitialized();
550
+ this.ensureNotTransitioning();
551
+ const hexId = (0, ecies_lib_1.uint8ArrayToHex)(member.idBytes);
552
+ // Check if member already exists
553
+ const existing = await this.db.getMember(hexId);
554
+ if (existing && existing.isActive) {
555
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.MemberAlreadyExists);
556
+ }
557
+ // Save the new member
558
+ const quorumMember = {
559
+ id: hexId,
560
+ publicKey: member.publicKey,
561
+ metadata,
562
+ isActive: true,
563
+ createdAt: new Date(),
564
+ updatedAt: new Date(),
565
+ };
566
+ await this.db.saveMember(quorumMember);
567
+ // Capture previous epoch info before creating the new one
568
+ const previousEpochNumber = this.currentEpochData?.epochNumber ?? 0;
569
+ const previousThreshold = this.currentEpochData?.threshold ?? 1;
570
+ const previousMemberIds = this.currentEpochData?.memberIds ?? [];
571
+ // Build new member list
572
+ const newMemberIds = [...previousMemberIds, hexId];
573
+ // Determine mode and threshold for the new epoch
574
+ const newMode = newMemberIds.length >= this.configuredThreshold
575
+ ? quorumOperationalMode_1.QuorumOperationalMode.Quorum
576
+ : quorumOperationalMode_1.QuorumOperationalMode.Bootstrap;
577
+ const newThreshold = newMode === quorumOperationalMode_1.QuorumOperationalMode.Bootstrap
578
+ ? Math.max(newMemberIds.length, 1)
579
+ : this.configuredThreshold;
580
+ // Create next epoch with the updated membership
581
+ const newEpoch = await this.createNextEpoch(newMemberIds, newThreshold, newMode);
582
+ // Emit member_added audit entry
583
+ await this.emitAuditEntry('member_added', {
584
+ memberId: hexId,
585
+ epochNumber: newEpoch.epochNumber,
586
+ previousEpochNumber,
587
+ memberCount: newMemberIds.length,
588
+ }, hexId);
589
+ // Trigger batched share redistribution for documents in the previous epoch
590
+ if (previousEpochNumber > 0 && previousMemberIds.length > 0) {
591
+ const config = {
592
+ batchSize: redistributionConfig?.batchSize ?? 100,
593
+ continueOnFailure: redistributionConfig?.continueOnFailure ?? true,
594
+ onProgress: redistributionConfig?.onProgress,
595
+ };
596
+ await this.emitAuditEntry('share_redistribution_started', {
597
+ reason: 'member_added',
598
+ memberId: hexId,
599
+ fromEpoch: previousEpochNumber,
600
+ toEpoch: newEpoch.epochNumber,
601
+ });
602
+ try {
603
+ // Resolve active members for redistribution
604
+ const activeQuorumMembers = await this.db.listActiveMembers();
605
+ const newMemberObjects = this.resolveMembersFromRecords(activeQuorumMembers);
606
+ // We need threshold members with private keys to decrypt existing shares.
607
+ // In a real system, these would come from approved votes or key holders.
608
+ // For redistribution triggered by addMember, we use the members that
609
+ // have shares in the existing documents. The SealingService.decryptShares
610
+ // method handles the actual decryption using member private keys.
611
+ // Since we don't have private keys here, we pass the member objects
612
+ // and let the redistribution handle documents that have accessible shares.
613
+ const failedDocIds = await this.redistributeDocuments(previousEpochNumber, newMemberObjects, newMemberObjects, newThreshold, {
614
+ totalShares: previousMemberIds.length,
615
+ threshold: previousThreshold,
616
+ }, config);
617
+ if (failedDocIds.length > 0) {
618
+ await this.emitAuditEntry('share_redistribution_failed', {
619
+ reason: 'member_added',
620
+ memberId: hexId,
621
+ failedDocumentIds: failedDocIds,
622
+ failedCount: failedDocIds.length,
623
+ });
624
+ }
625
+ else {
626
+ await this.emitAuditEntry('share_redistribution_completed', {
627
+ reason: 'member_added',
628
+ memberId: hexId,
629
+ fromEpoch: previousEpochNumber,
630
+ toEpoch: newEpoch.epochNumber,
631
+ });
632
+ }
633
+ }
634
+ catch (redistributionError) {
635
+ await this.emitAuditEntry('share_redistribution_failed', {
636
+ reason: 'member_added',
637
+ memberId: hexId,
638
+ error: redistributionError instanceof Error
639
+ ? redistributionError.message
640
+ : String(redistributionError),
641
+ });
642
+ }
643
+ }
644
+ return newEpoch;
645
+ }
646
+ /**
647
+ * Remove a member from the quorum.
648
+ *
649
+ * Validates remaining count >= threshold, removes the member,
650
+ * increments the epoch, triggers share redistribution with fresh
651
+ * polynomial coefficients, and emits audit entries for member_removed
652
+ * and share_redistribution_*.
653
+ *
654
+ * @param memberId - ID of the member to remove
655
+ * @param redistributionConfig - Optional redistribution configuration
656
+ * @returns The new QuorumEpoch after member removal
657
+ * @throws QuorumError with InsufficientRemainingMembers if removal would drop below threshold
658
+ * @throws QuorumError with MemberNotFound if the member is not in the current epoch
659
+ */
660
+ async removeMember(memberId, redistributionConfig) {
661
+ this.ensureInitialized();
662
+ this.ensureNotTransitioning();
663
+ const currentMemberIds = this.currentEpochData?.memberIds ?? [];
664
+ // Validate member exists in current epoch
665
+ if (!currentMemberIds.includes(memberId)) {
666
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.MemberNotFound);
667
+ }
668
+ // Validate remaining count >= threshold after removal
669
+ const remainingCount = currentMemberIds.length - 1;
670
+ const currentThreshold = this.currentEpochData?.threshold ?? this.configuredThreshold;
671
+ if (remainingCount < currentThreshold) {
672
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.InsufficientRemainingMembers);
673
+ }
674
+ // Mark the member as inactive in the database
675
+ const memberRecord = await this.db.getMember(memberId);
676
+ if (memberRecord) {
677
+ const updatedMember = {
678
+ ...memberRecord,
679
+ isActive: false,
680
+ updatedAt: new Date(),
681
+ };
682
+ await this.db.saveMember(updatedMember);
683
+ }
684
+ // Capture previous epoch info before creating the new one
685
+ const previousEpochNumber = this.currentEpochData?.epochNumber ?? 0;
686
+ const previousThreshold = this.currentEpochData?.threshold ?? 1;
687
+ // Build new member list without the removed member
688
+ const newMemberIds = currentMemberIds.filter((id) => id !== memberId);
689
+ // Determine mode and threshold for the new epoch
690
+ const currentMode = this.mode ?? quorumOperationalMode_1.QuorumOperationalMode.Bootstrap;
691
+ const newThreshold = currentMode === quorumOperationalMode_1.QuorumOperationalMode.Bootstrap
692
+ ? Math.max(newMemberIds.length, 1)
693
+ : this.configuredThreshold;
694
+ // Create next epoch with the updated membership
695
+ const newEpoch = await this.createNextEpoch(newMemberIds, newThreshold, currentMode);
696
+ // Emit member_removed audit entry
697
+ await this.emitAuditEntry('member_removed', {
698
+ memberId,
699
+ epochNumber: newEpoch.epochNumber,
700
+ previousEpochNumber,
701
+ memberCount: newMemberIds.length,
702
+ }, memberId);
703
+ // Trigger batched share redistribution with fresh polynomial coefficients
704
+ if (previousEpochNumber > 0 && newMemberIds.length > 0) {
705
+ const config = {
706
+ batchSize: redistributionConfig?.batchSize ?? 100,
707
+ continueOnFailure: redistributionConfig?.continueOnFailure ?? true,
708
+ onProgress: redistributionConfig?.onProgress,
709
+ };
710
+ await this.emitAuditEntry('share_redistribution_started', {
711
+ reason: 'member_removed',
712
+ memberId,
713
+ fromEpoch: previousEpochNumber,
714
+ toEpoch: newEpoch.epochNumber,
715
+ });
716
+ try {
717
+ // Resolve remaining active members for redistribution
718
+ const activeQuorumMembers = await this.db.listActiveMembers();
719
+ // Filter out the removed member
720
+ const remainingQuorumMembers = activeQuorumMembers.filter((m) => m.id !== memberId);
721
+ const newMemberObjects = this.resolveMembersFromRecords(remainingQuorumMembers);
722
+ const failedDocIds = await this.redistributeDocuments(previousEpochNumber, newMemberObjects, newMemberObjects, newThreshold, {
723
+ totalShares: currentMemberIds.length,
724
+ threshold: previousThreshold,
725
+ }, config);
726
+ if (failedDocIds.length > 0) {
727
+ await this.emitAuditEntry('share_redistribution_failed', {
728
+ reason: 'member_removed',
729
+ memberId,
730
+ failedDocumentIds: failedDocIds,
731
+ failedCount: failedDocIds.length,
732
+ });
733
+ }
734
+ else {
735
+ await this.emitAuditEntry('share_redistribution_completed', {
736
+ reason: 'member_removed',
737
+ memberId,
738
+ fromEpoch: previousEpochNumber,
739
+ toEpoch: newEpoch.epochNumber,
740
+ });
741
+ }
742
+ }
743
+ catch (redistributionError) {
744
+ await this.emitAuditEntry('share_redistribution_failed', {
745
+ reason: 'member_removed',
746
+ memberId,
747
+ error: redistributionError instanceof Error
748
+ ? redistributionError.message
749
+ : String(redistributionError),
750
+ });
751
+ }
752
+ }
753
+ return newEpoch;
754
+ }
755
+ /**
756
+ * Determine the required threshold for a proposal based on inner quorum routing.
757
+ *
758
+ * When member count > 20 and the current epoch has innerQuorumMemberIds,
759
+ * routine operations use the inner quorum size as threshold,
760
+ * while critical operations use the full membership threshold.
761
+ *
762
+ * @param actionType - The proposal action type
763
+ * @returns The required vote threshold
764
+ */
765
+ getRequiredThreshold(actionType) {
766
+ if (!this.currentEpochData) {
767
+ return this.configuredThreshold;
768
+ }
769
+ const memberCount = this.currentEpochData.memberIds.length;
770
+ const innerQuorum = this.currentEpochData.innerQuorumMemberIds;
771
+ // Inner quorum routing: when members > 20 and inner quorum is defined
772
+ if (memberCount > QuorumStateMachine.INNER_QUORUM_MEMBER_THRESHOLD &&
773
+ innerQuorum &&
774
+ innerQuorum.length > 0) {
775
+ if (QuorumStateMachine.ROUTINE_ACTION_TYPES.has(actionType)) {
776
+ // Routine operations use inner quorum threshold (majority of inner quorum)
777
+ return Math.ceil(innerQuorum.length / 2);
778
+ }
779
+ // Critical operations use full membership threshold
780
+ }
781
+ return this.currentEpochData.threshold;
782
+ }
783
+ /**
784
+ * Submit a proposal for quorum voting.
785
+ *
786
+ * Validates the proposer is an active member, validates description length,
787
+ * validates IDENTITY_DISCLOSURE has an attachment, assigns a unique ID,
788
+ * stores as pending, announces via gossip, and emits audit entry.
789
+ *
790
+ * @param proposal - The proposal input
791
+ * @returns The created Proposal with assigned ID and status
792
+ * @throws QuorumError with MissingAttachment if IDENTITY_DISCLOSURE lacks attachmentCblId
793
+ */
794
+ async submitProposal(proposal) {
795
+ this.ensureInitialized();
796
+ this.ensureNotTransitioning();
797
+ // Validate description length (Req 5.3)
798
+ if (proposal.description.length > QuorumStateMachine.MAX_DESCRIPTION_LENGTH) {
799
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.DuplicateProposal);
800
+ }
801
+ // Validate IDENTITY_DISCLOSURE requires attachment (Req 13.3)
802
+ if (proposal.actionType === proposalActionType_1.ProposalActionType.IDENTITY_DISCLOSURE &&
803
+ !proposal.attachmentCblId) {
804
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.MissingAttachment);
805
+ }
806
+ if (!this.currentEpochData) {
807
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.Uninitialized);
808
+ }
809
+ // Determine the required threshold based on inner quorum routing
810
+ const requiredThreshold = this.getRequiredThreshold(proposal.actionType);
811
+ // Assign unique ID and create the full proposal
812
+ const proposalId = (0, uuid_1.v4)();
813
+ const now = new Date();
814
+ const fullProposal = {
815
+ id: proposalId,
816
+ description: proposal.description,
817
+ actionType: proposal.actionType,
818
+ actionPayload: proposal.actionPayload,
819
+ proposerMemberId: (proposal.actionPayload['proposerMemberId'] ??
820
+ this.currentEpochData.memberIds[0]),
821
+ status: proposalStatus_1.ProposalStatus.Pending,
822
+ requiredThreshold,
823
+ expiresAt: proposal.expiresAt,
824
+ createdAt: now,
825
+ attachmentCblId: proposal.attachmentCblId,
826
+ epochNumber: this.currentEpochData.epochNumber,
827
+ };
828
+ // Store as pending in the database
829
+ await this.db.saveProposal(fullProposal);
830
+ // Announce via gossip
831
+ const gossipMetadata = {
832
+ proposalId: fullProposal.id,
833
+ description: fullProposal.description,
834
+ actionType: fullProposal.actionType,
835
+ actionPayload: JSON.stringify(fullProposal.actionPayload),
836
+ proposerMemberId: fullProposal.proposerMemberId,
837
+ expiresAt: fullProposal.expiresAt,
838
+ requiredThreshold: fullProposal.requiredThreshold,
839
+ attachmentCblId: fullProposal.attachmentCblId,
840
+ };
841
+ await this.gossipService.announceQuorumProposal(gossipMetadata);
842
+ // Emit audit entry
843
+ await this.emitAuditEntry('proposal_created', {
844
+ proposalId: fullProposal.id,
845
+ actionType: fullProposal.actionType,
846
+ proposerMemberId: fullProposal.proposerMemberId,
847
+ requiredThreshold: fullProposal.requiredThreshold,
848
+ epochNumber: fullProposal.epochNumber,
849
+ hasAttachment: !!fullProposal.attachmentCblId,
850
+ });
851
+ // Update metrics
852
+ this.metricsData.proposalsTotal++;
853
+ this.metricsData.proposalsPending++;
854
+ return fullProposal;
855
+ }
856
+ /**
857
+ * Submit a vote on a pending proposal.
858
+ *
859
+ * Validates the proposal exists and is pending, validates the voter is an
860
+ * active member, checks for duplicate votes, stores the vote, announces
861
+ * via gossip, emits audit entry, and triggers vote tallying.
862
+ *
863
+ * @param vote - The vote input
864
+ * @throws QuorumError with ProposalExpired if proposal is not pending
865
+ * @throws QuorumError with DuplicateVote if voter already voted
866
+ * @throws QuorumError with VoterNotOnProposal if voter is not an active member
867
+ */
868
+ async submitVote(vote) {
869
+ this.ensureInitialized();
870
+ this.ensureNotTransitioning();
871
+ // Validate proposal exists
872
+ const proposal = await this.db.getProposal(vote.proposalId);
873
+ if (!proposal) {
874
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.DocumentNotFound);
875
+ }
876
+ // Check expiration before processing
877
+ if (new Date() > proposal.expiresAt) {
878
+ await this.expireProposal(proposal);
879
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.ProposalExpired);
880
+ }
881
+ // Validate proposal is still pending
882
+ if (proposal.status !== proposalStatus_1.ProposalStatus.Pending) {
883
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.ProposalExpired);
884
+ }
885
+ // Validate voter is an active member
886
+ if (!this.currentEpochData) {
887
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.Uninitialized);
888
+ }
889
+ const resolvedVoterId = vote.voterMemberId ?? this.currentEpochData.memberIds[0];
890
+ // Check voter is in the current epoch's member list
891
+ const isActiveMember = this.currentEpochData.memberIds.includes(resolvedVoterId);
892
+ if (!isActiveMember) {
893
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.VoterNotOnProposal);
894
+ }
895
+ // Check for duplicate votes
896
+ const existingVotes = await this.db.getVotesForProposal(vote.proposalId);
897
+ const alreadyVoted = existingVotes.some((v) => v.voterMemberId === resolvedVoterId);
898
+ if (alreadyVoted) {
899
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.DuplicateVote);
900
+ }
901
+ // Create and store the vote
902
+ const fullVote = {
903
+ proposalId: vote.proposalId,
904
+ voterMemberId: resolvedVoterId,
905
+ decision: vote.decision,
906
+ comment: vote.comment,
907
+ createdAt: new Date(),
908
+ };
909
+ await this.db.saveVote(fullVote);
910
+ // Announce via gossip
911
+ const gossipMetadata = {
912
+ proposalId: fullVote.proposalId,
913
+ voterMemberId: fullVote.voterMemberId,
914
+ decision: fullVote.decision,
915
+ comment: fullVote.comment,
916
+ };
917
+ await this.gossipService.announceQuorumVote(gossipMetadata);
918
+ // Emit audit entry
919
+ await this.emitAuditEntry('vote_cast', {
920
+ proposalId: fullVote.proposalId,
921
+ voterMemberId: fullVote.voterMemberId,
922
+ decision: fullVote.decision,
923
+ });
924
+ // Tally votes to check if threshold is reached
925
+ await this.tallyVotes(proposal);
926
+ }
927
+ /**
928
+ * Tally votes for a proposal.
929
+ *
930
+ * Counts approve and reject votes. If approve count >= threshold, marks
931
+ * the proposal as approved and dispatches the action. If reject count
932
+ * makes approval impossible, marks the proposal as rejected.
933
+ *
934
+ * @param proposal - The proposal to tally votes for
935
+ */
936
+ async tallyVotes(proposal) {
937
+ const votes = await this.db.getVotesForProposal(proposal.id);
938
+ // Count distinct votes (duplicates already prevented by submitVote)
939
+ const approveCount = votes.filter((v) => v.decision === 'approve').length;
940
+ const rejectCount = votes.filter((v) => v.decision === 'reject').length;
941
+ // Check if threshold is reached for approval
942
+ if (approveCount >= proposal.requiredThreshold) {
943
+ // Mark as approved
944
+ const approvedProposal = {
945
+ ...proposal,
946
+ status: proposalStatus_1.ProposalStatus.Approved,
947
+ };
948
+ await this.db.saveProposal(approvedProposal);
949
+ // Emit proposal_approved audit entry
950
+ await this.emitAuditEntry('proposal_approved', {
951
+ proposalId: proposal.id,
952
+ actionType: proposal.actionType,
953
+ approveCount,
954
+ rejectCount,
955
+ requiredThreshold: proposal.requiredThreshold,
956
+ });
957
+ // Update metrics
958
+ this.metricsData.proposalsPending = Math.max(0, this.metricsData.proposalsPending - 1);
959
+ // Execute the action
960
+ await this.executeProposalAction(approvedProposal);
961
+ return;
962
+ }
963
+ // Check if approval is impossible (remaining possible votes can't reach threshold)
964
+ const totalMembers = this.currentEpochData?.memberIds.length ?? 0;
965
+ const totalVotesCast = approveCount + rejectCount;
966
+ const remainingVotes = totalMembers - totalVotesCast;
967
+ const maxPossibleApprovals = approveCount + remainingVotes;
968
+ if (maxPossibleApprovals < proposal.requiredThreshold) {
969
+ // Approval is impossible — mark as rejected
970
+ const rejectedProposal = {
971
+ ...proposal,
972
+ status: proposalStatus_1.ProposalStatus.Rejected,
973
+ };
974
+ await this.db.saveProposal(rejectedProposal);
975
+ // Emit proposal_rejected audit entry
976
+ await this.emitAuditEntry('proposal_rejected', {
977
+ proposalId: proposal.id,
978
+ actionType: proposal.actionType,
979
+ approveCount,
980
+ rejectCount,
981
+ requiredThreshold: proposal.requiredThreshold,
982
+ });
983
+ // Update metrics
984
+ this.metricsData.proposalsPending = Math.max(0, this.metricsData.proposalsPending - 1);
985
+ }
986
+ }
987
+ /**
988
+ * Mark a proposal as expired and emit audit entry.
989
+ *
990
+ * @param proposal - The proposal to expire
991
+ */
992
+ async expireProposal(proposal) {
993
+ if (proposal.status !== proposalStatus_1.ProposalStatus.Pending) {
994
+ return;
995
+ }
996
+ const expiredProposal = {
997
+ ...proposal,
998
+ status: proposalStatus_1.ProposalStatus.Expired,
999
+ };
1000
+ await this.db.saveProposal(expiredProposal);
1001
+ await this.emitAuditEntry('proposal_expired', {
1002
+ proposalId: proposal.id,
1003
+ actionType: proposal.actionType,
1004
+ expiresAt: proposal.expiresAt.toISOString(),
1005
+ });
1006
+ this.metricsData.proposalsPending = Math.max(0, this.metricsData.proposalsPending - 1);
1007
+ }
1008
+ /**
1009
+ * Execute the action associated with an approved proposal.
1010
+ *
1011
+ * Dispatches to the appropriate handler based on ProposalActionType.
1012
+ * Simple actions are fully implemented; complex ones are stubs that log.
1013
+ *
1014
+ * @param proposal - The approved proposal whose action to execute
1015
+ */
1016
+ async executeProposalAction(proposal) {
1017
+ switch (proposal.actionType) {
1018
+ case proposalActionType_1.ProposalActionType.ADD_MEMBER:
1019
+ await this.executeAddMember(proposal);
1020
+ break;
1021
+ case proposalActionType_1.ProposalActionType.REMOVE_MEMBER:
1022
+ await this.executeRemoveMember(proposal);
1023
+ break;
1024
+ case proposalActionType_1.ProposalActionType.CHANGE_THRESHOLD:
1025
+ await this.executeChangeThreshold(proposal);
1026
+ break;
1027
+ case proposalActionType_1.ProposalActionType.TRANSITION_TO_QUORUM_MODE:
1028
+ await this.initiateTransition();
1029
+ break;
1030
+ case proposalActionType_1.ProposalActionType.UNSEAL_DOCUMENT:
1031
+ await this.executeUnsealDocument(proposal);
1032
+ break;
1033
+ case proposalActionType_1.ProposalActionType.IDENTITY_DISCLOSURE:
1034
+ await this.executeIdentityDisclosure(proposal);
1035
+ break;
1036
+ case proposalActionType_1.ProposalActionType.REGISTER_ALIAS:
1037
+ await this.executeRegisterAlias(proposal);
1038
+ break;
1039
+ case proposalActionType_1.ProposalActionType.DEREGISTER_ALIAS:
1040
+ await this.executeDeregisterAlias(proposal);
1041
+ break;
1042
+ case proposalActionType_1.ProposalActionType.EXTEND_STATUTE:
1043
+ await this.executeExtendStatute(proposal);
1044
+ break;
1045
+ case proposalActionType_1.ProposalActionType.CHANGE_INNER_QUORUM:
1046
+ await this.executeChangeInnerQuorum(proposal);
1047
+ break;
1048
+ case proposalActionType_1.ProposalActionType.CUSTOM:
1049
+ await this.executeCustomAction(proposal);
1050
+ break;
1051
+ }
1052
+ }
1053
+ /**
1054
+ * 11.8.1 ADD_MEMBER — stub that calls addMember with payload data.
1055
+ */
1056
+ async executeAddMember(proposal) {
1057
+ // Stub: In a full implementation, the member object would be constructed
1058
+ // from the proposal's actionPayload. For now, log the approval.
1059
+ await this.emitAuditEntry('member_added', {
1060
+ proposalId: proposal.id,
1061
+ actionType: proposal.actionType,
1062
+ stub: true,
1063
+ payload: proposal.actionPayload,
1064
+ });
1065
+ }
1066
+ /**
1067
+ * 11.8.2 REMOVE_MEMBER — stub that calls removeMember with payload data.
1068
+ */
1069
+ async executeRemoveMember(proposal) {
1070
+ // Stub: In a full implementation, removeMember would be called with
1071
+ // the memberId from actionPayload. For now, log the approval.
1072
+ await this.emitAuditEntry('member_removed', {
1073
+ proposalId: proposal.id,
1074
+ actionType: proposal.actionType,
1075
+ stub: true,
1076
+ payload: proposal.actionPayload,
1077
+ });
1078
+ }
1079
+ /**
1080
+ * 11.8.3 CHANGE_THRESHOLD — update threshold and trigger share redistribution.
1081
+ */
1082
+ async executeChangeThreshold(proposal) {
1083
+ const newThreshold = proposal.actionPayload['newThreshold'];
1084
+ if (typeof newThreshold !== 'number' || newThreshold < 1) {
1085
+ return;
1086
+ }
1087
+ this.configuredThreshold = newThreshold;
1088
+ if (this.currentEpochData) {
1089
+ const newEpoch = await this.createNextEpoch(this.currentEpochData.memberIds, newThreshold, this.currentEpochData.mode);
1090
+ await this.emitAuditEntry('epoch_created', {
1091
+ proposalId: proposal.id,
1092
+ reason: 'threshold_change',
1093
+ newThreshold,
1094
+ epochNumber: newEpoch.epochNumber,
1095
+ });
1096
+ }
1097
+ }
1098
+ /**
1099
+ * 11.8.5 UNSEAL_DOCUMENT — stub (collect shares, unseal).
1100
+ */
1101
+ async executeUnsealDocument(proposal) {
1102
+ // Stub: In a full implementation, encrypted shares from approve votes
1103
+ // would be collected, decrypted, and used to unseal the document.
1104
+ await this.emitAuditEntry('proposal_approved', {
1105
+ proposalId: proposal.id,
1106
+ actionType: proposal.actionType,
1107
+ stub: true,
1108
+ documentId: proposal.actionPayload['documentId'],
1109
+ });
1110
+ }
1111
+ /**
1112
+ * 11.8.6 IDENTITY_DISCLOSURE — check statute of limitations, reject if expired/deleted.
1113
+ *
1114
+ * When the target IdentityRecoveryRecord has expired or been deleted by the
1115
+ * expiration scheduler, throws IdentityPermanentlyUnrecoverable (Req 17.6).
1116
+ */
1117
+ async executeIdentityDisclosure(proposal) {
1118
+ const targetRecordId = proposal.actionPayload['targetRecordId'];
1119
+ // Check if the identity record still exists (not expired/deleted)
1120
+ if (targetRecordId) {
1121
+ const record = await this.db.getIdentityRecord(targetRecordId);
1122
+ if (!record) {
1123
+ // Identity has been permanently deleted by expiration scheduler
1124
+ await this.emitAuditEntry('identity_disclosure_rejected', {
1125
+ proposalId: proposal.id,
1126
+ reason: 'identity_permanently_unrecoverable',
1127
+ targetRecordId,
1128
+ });
1129
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.IdentityPermanentlyUnrecoverable);
1130
+ }
1131
+ // Check if record has expired (statute of limitations exceeded)
1132
+ if (new Date() > record.expiresAt) {
1133
+ await this.emitAuditEntry('identity_disclosure_expired', {
1134
+ proposalId: proposal.id,
1135
+ targetRecordId,
1136
+ expiresAt: record.expiresAt.toISOString(),
1137
+ });
1138
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.IdentityPermanentlyUnrecoverable);
1139
+ }
1140
+ }
1141
+ await this.emitAuditEntry('identity_disclosure_approved', {
1142
+ proposalId: proposal.id,
1143
+ actionType: proposal.actionType,
1144
+ targetMemberId: proposal.actionPayload['targetMemberId'],
1145
+ attachmentCblId: proposal.attachmentCblId,
1146
+ });
1147
+ }
1148
+ /**
1149
+ * 11.8.7 REGISTER_ALIAS — delegate to AliasRegistry.registerAlias().
1150
+ */
1151
+ async executeRegisterAlias(proposal) {
1152
+ const aliasName = proposal.actionPayload['aliasName'];
1153
+ const ownerMemberId = proposal.actionPayload['ownerMemberId'];
1154
+ const ownerPublicKeyHex = proposal.actionPayload['ownerPublicKey'];
1155
+ if (this.aliasRegistry && aliasName && ownerMemberId && ownerPublicKeyHex) {
1156
+ const ownerPublicKey = new Uint8Array(Buffer.from(ownerPublicKeyHex, 'hex'));
1157
+ await this.aliasRegistry.registerAlias(aliasName, ownerMemberId, ownerPublicKey);
1158
+ }
1159
+ await this.emitAuditEntry('alias_registered', {
1160
+ proposalId: proposal.id,
1161
+ actionType: proposal.actionType,
1162
+ aliasName: aliasName ?? proposal.actionPayload['aliasName'],
1163
+ });
1164
+ }
1165
+ /**
1166
+ * 11.8.8 DEREGISTER_ALIAS — delegate to AliasRegistry.deregisterAlias().
1167
+ */
1168
+ async executeDeregisterAlias(proposal) {
1169
+ const aliasName = proposal.actionPayload['aliasName'];
1170
+ if (this.aliasRegistry && aliasName) {
1171
+ await this.aliasRegistry.deregisterAlias(aliasName);
1172
+ }
1173
+ await this.emitAuditEntry('alias_deregistered', {
1174
+ proposalId: proposal.id,
1175
+ actionType: proposal.actionType,
1176
+ aliasName: aliasName ?? proposal.actionPayload['aliasName'],
1177
+ });
1178
+ }
1179
+ /**
1180
+ * 11.8.9 EXTEND_STATUTE — update expiresAt on the target IdentityRecoveryRecord.
1181
+ */
1182
+ async executeExtendStatute(proposal) {
1183
+ const targetRecordId = proposal.actionPayload['targetRecordId'];
1184
+ const newExpiresAt = proposal.actionPayload['newExpiresAt'];
1185
+ if (targetRecordId && newExpiresAt) {
1186
+ const record = await this.db.getIdentityRecord(targetRecordId);
1187
+ if (record) {
1188
+ const updatedRecord = {
1189
+ ...record,
1190
+ expiresAt: new Date(newExpiresAt),
1191
+ };
1192
+ await this.db.saveIdentityRecord(updatedRecord);
1193
+ await this.emitAuditEntry('proposal_approved', {
1194
+ proposalId: proposal.id,
1195
+ actionType: proposal.actionType,
1196
+ targetRecordId,
1197
+ newExpiresAt,
1198
+ });
1199
+ }
1200
+ }
1201
+ }
1202
+ /**
1203
+ * 11.8.10 CHANGE_INNER_QUORUM — update innerQuorumMemberIds on current epoch.
1204
+ */
1205
+ async executeChangeInnerQuorum(proposal) {
1206
+ const innerQuorumMemberIds = proposal.actionPayload['innerQuorumMemberIds'];
1207
+ if (this.currentEpochData && innerQuorumMemberIds) {
1208
+ // Create a new epoch with the updated inner quorum
1209
+ const newEpoch = await this.createNextEpoch(this.currentEpochData.memberIds, this.currentEpochData.threshold, this.currentEpochData.mode);
1210
+ // Update the epoch with inner quorum member IDs
1211
+ newEpoch.innerQuorumMemberIds = innerQuorumMemberIds;
1212
+ await this.db.saveEpoch(newEpoch);
1213
+ await this.emitAuditEntry('epoch_created', {
1214
+ proposalId: proposal.id,
1215
+ reason: 'inner_quorum_change',
1216
+ innerQuorumMemberIds,
1217
+ epochNumber: newEpoch.epochNumber,
1218
+ });
1219
+ }
1220
+ }
1221
+ /**
1222
+ * 11.8.11 CUSTOM — log approval without automated execution.
1223
+ */
1224
+ async executeCustomAction(proposal) {
1225
+ // Custom actions are logged but not automatically executed
1226
+ await this.emitAuditEntry('proposal_approved', {
1227
+ proposalId: proposal.id,
1228
+ actionType: proposal.actionType,
1229
+ custom: true,
1230
+ payload: proposal.actionPayload,
1231
+ });
1232
+ }
1233
+ /**
1234
+ * Get a proposal by its ID.
1235
+ */
1236
+ async getProposal(proposalId) {
1237
+ this.ensureInitialized();
1238
+ return this.db.getProposal(proposalId);
1239
+ }
1240
+ /**
1241
+ * Seal a document using the appropriate mode (bootstrap or quorum).
1242
+ *
1243
+ * In bootstrap mode: delegates to quorumSealBootstrap with effective threshold = member count.
1244
+ * In quorum mode: delegates to quorumSeal with configured threshold.
1245
+ * Tags document with epoch number and sealedUnderBootstrap flag.
1246
+ *
1247
+ * @throws QuorumError with TransitionInProgress if a transition ceremony is active
1248
+ */
1249
+ async sealDocument(agent, document, memberIds, sharesRequired) {
1250
+ this.ensureInitialized();
1251
+ this.ensureNotTransitioning();
1252
+ if (!this.currentEpochData) {
1253
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.Uninitialized);
1254
+ }
1255
+ const isBootstrap = this.mode === quorumOperationalMode_1.QuorumOperationalMode.Bootstrap;
1256
+ // Resolve members from the active member list
1257
+ const activeMembers = await this.db.listActiveMembers();
1258
+ const memberMap = new Map();
1259
+ for (const m of activeMembers) {
1260
+ memberMap.set(m.id, m);
1261
+ }
1262
+ // Validate all requested member IDs exist
1263
+ for (const mid of memberIds) {
1264
+ if (!memberMap.has(mid)) {
1265
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.MemberNotFound);
1266
+ }
1267
+ }
1268
+ // Determine effective threshold
1269
+ const effectiveThreshold = isBootstrap
1270
+ ? Math.max(memberIds.length, 1)
1271
+ : (sharesRequired ?? this.currentEpochData.threshold);
1272
+ // Delegate to SealingService
1273
+ let sealedDoc;
1274
+ if (isBootstrap) {
1275
+ sealedDoc = await this.sealingService.quorumSealBootstrap(agent, document, [agent], effectiveThreshold);
1276
+ }
1277
+ else {
1278
+ sealedDoc = await this.sealingService.quorumSeal(agent, document, [agent], effectiveThreshold);
1279
+ }
1280
+ const docHexId = (0, ecies_lib_1.uint8ArrayToHex)(sealedDoc.enhancedProvider.toBytes(sealedDoc.id));
1281
+ // Save to database
1282
+ await this.db.saveDocument(sealedDoc);
1283
+ return {
1284
+ documentId: docHexId,
1285
+ encryptedData: sealedDoc.encryptedData,
1286
+ memberIds,
1287
+ sharesRequired: sealedDoc.sharesRequired,
1288
+ createdAt: sealedDoc.dateCreated,
1289
+ };
1290
+ }
1291
+ /**
1292
+ * Unseal a document.
1293
+ *
1294
+ * Verifies checksum (SHA-3) and creator signature using constant-time
1295
+ * comparison before delegating to SealingService.
1296
+ * Returns generic error on failure without revealing which check failed (Req 8.4-8.6).
1297
+ */
1298
+ async unsealDocument(documentId, membersWithPrivateKey) {
1299
+ this.ensureInitialized();
1300
+ this.ensureNotTransitioning();
1301
+ const doc = await this.db.getDocument(documentId);
1302
+ if (!doc) {
1303
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.DocumentNotFound);
1304
+ }
1305
+ // Verify checksum using constant-time comparison (Req 8.5)
1306
+ const calculatedChecksum = this.checksumService.calculateChecksum(doc.encryptedData);
1307
+ const checksumValid = constantTimeEqual(calculatedChecksum.toUint8Array(), doc.checksum.toUint8Array());
1308
+ // Verify creator signature using constant-time comparison (Req 8.5)
1309
+ let signatureValid = false;
1310
+ try {
1311
+ const expectedSignature = doc.creator.sign(doc.checksum.toUint8Array());
1312
+ signatureValid = constantTimeEqual(doc.signature, expectedSignature);
1313
+ }
1314
+ catch {
1315
+ signatureValid = false;
1316
+ }
1317
+ // Return generic error without revealing which check failed (Req 8.6)
1318
+ if (!checksumValid || !signatureValid) {
1319
+ throw new quorumError_1.QuorumError(quorumErrorType_1.QuorumErrorType.UnableToRestoreDocument);
1320
+ }
1321
+ // Delegate to SealingService for actual decryption
1322
+ return this.sealingService.quorumUnseal(doc, membersWithPrivateKey);
1323
+ }
1324
+ /** Get the current epoch. */
1325
+ async getCurrentEpoch() {
1326
+ this.ensureInitialized();
1327
+ if (this.currentEpochData) {
1328
+ return this.currentEpochData;
1329
+ }
1330
+ const epoch = await this.db.getCurrentEpoch();
1331
+ this.currentEpochData = epoch;
1332
+ return epoch;
1333
+ }
1334
+ /** Get a specific epoch by number. */
1335
+ async getEpoch(epochNumber) {
1336
+ this.ensureInitialized();
1337
+ return this.db.getEpoch(epochNumber);
1338
+ }
1339
+ /** Get quorum system metrics for monitoring. */
1340
+ async getMetrics() {
1341
+ const activeMembers = this.initialized
1342
+ ? await this.db.listActiveMembers()
1343
+ : [];
1344
+ const currentEpochNumber = this.currentEpochData?.epochNumber ?? 0;
1345
+ return {
1346
+ proposals: {
1347
+ total: this.metricsData.proposalsTotal,
1348
+ pending: this.metricsData.proposalsPending,
1349
+ },
1350
+ votes: {
1351
+ latency_ms: this.metricsData.votesLatencyMs,
1352
+ },
1353
+ redistribution: {
1354
+ progress: this.metricsData.redistributionProgress,
1355
+ failures: this.metricsData.redistributionFailures,
1356
+ },
1357
+ members: {
1358
+ active: activeMembers.length,
1359
+ },
1360
+ epoch: {
1361
+ current: currentEpochNumber,
1362
+ },
1363
+ expiration: {
1364
+ last_run: this.metricsData.expirationLastRun,
1365
+ deleted_total: this.metricsData.expirationDeletedTotal,
1366
+ },
1367
+ };
1368
+ }
1369
+ /** Get the configured threshold for this quorum. */
1370
+ getConfiguredThreshold() {
1371
+ return this.configuredThreshold;
1372
+ }
1373
+ }
1374
+ exports.QuorumStateMachine = QuorumStateMachine;
1375
+ /**
1376
+ * Maximum allowed description length for proposals.
1377
+ */
1378
+ QuorumStateMachine.MAX_DESCRIPTION_LENGTH = 4096;
1379
+ /**
1380
+ * Member count threshold above which inner quorum routing is used.
1381
+ */
1382
+ QuorumStateMachine.INNER_QUORUM_MEMBER_THRESHOLD = 20;
1383
+ /**
1384
+ * Action types considered routine for inner quorum routing.
1385
+ * When member count > 20 and an inner quorum exists, these use the inner quorum threshold.
1386
+ */
1387
+ QuorumStateMachine.ROUTINE_ACTION_TYPES = new Set([
1388
+ proposalActionType_1.ProposalActionType.ADD_MEMBER,
1389
+ proposalActionType_1.ProposalActionType.REMOVE_MEMBER,
1390
+ proposalActionType_1.ProposalActionType.UNSEAL_DOCUMENT,
1391
+ proposalActionType_1.ProposalActionType.REGISTER_ALIAS,
1392
+ proposalActionType_1.ProposalActionType.DEREGISTER_ALIAS,
1393
+ proposalActionType_1.ProposalActionType.EXTEND_STATUTE,
1394
+ proposalActionType_1.ProposalActionType.CUSTOM,
1395
+ ]);
1396
+ //# sourceMappingURL=quorumStateMachine.js.map