@openlup/platform 0.15.0 → 0.17.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 (531) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +633 -6
  3. package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
  4. package/dist/communications/accountingDocumentDelivery.js +33 -0
  5. package/dist/communications/authEmailError.d.ts +5 -0
  6. package/dist/communications/authEmailError.js +8 -0
  7. package/dist/communications/authEmailPorts.d.ts +2 -0
  8. package/dist/communications/authEmailPorts.js +1 -0
  9. package/dist/communications/authSendEmailHook.d.ts +35 -0
  10. package/dist/communications/authSendEmailHook.js +250 -0
  11. package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
  12. package/dist/communications/capturedTransactionalDelivery.js +14 -0
  13. package/dist/communications/communicationSync.d.ts +11 -0
  14. package/dist/communications/communicationSync.js +79 -0
  15. package/dist/communications/communicationSyncWorker.d.ts +68 -0
  16. package/dist/communications/communicationSyncWorker.js +218 -0
  17. package/dist/communications/consentPolicy.d.ts +19 -0
  18. package/dist/communications/consentPolicy.js +43 -0
  19. package/dist/communications/consentPurposeRegistry.d.ts +4 -0
  20. package/dist/communications/consentPurposeRegistry.js +24 -0
  21. package/dist/communications/contracts/abandonedCart.d.ts +27 -0
  22. package/dist/communications/contracts/abandonedCart.js +25 -0
  23. package/dist/communications/contracts/authEmail.d.ts +62 -0
  24. package/dist/communications/contracts/authEmail.js +1 -0
  25. package/dist/communications/contracts/backInStock.d.ts +28 -0
  26. package/dist/communications/contracts/backInStock.js +28 -0
  27. package/dist/communications/contracts/blocks.d.ts +24 -0
  28. package/dist/communications/contracts/blocks.js +23 -0
  29. package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
  30. package/dist/communications/contracts/checkoutExpired.js +25 -0
  31. package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
  32. package/dist/communications/contracts/checkoutRecovery.js +37 -0
  33. package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
  34. package/dist/communications/contracts/commerceEmailContent.js +1 -0
  35. package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
  36. package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
  37. package/dist/communications/contracts/consent.d.ts +12 -0
  38. package/dist/communications/contracts/consent.js +1 -0
  39. package/dist/communications/contracts/email.d.ts +182 -0
  40. package/dist/communications/contracts/email.js +1 -0
  41. package/dist/communications/contracts/guideCard.d.ts +3 -0
  42. package/dist/communications/contracts/guideCard.js +26 -0
  43. package/dist/communications/contracts/index.d.ts +51 -0
  44. package/dist/communications/contracts/index.js +42 -0
  45. package/dist/communications/contracts/inquiryContent.d.ts +32 -0
  46. package/dist/communications/contracts/inquiryContent.js +33 -0
  47. package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
  48. package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
  49. package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
  50. package/dist/communications/contracts/marketingAutomation.js +1 -0
  51. package/dist/communications/contracts/newsletter.d.ts +63 -0
  52. package/dist/communications/contracts/newsletter.js +1 -0
  53. package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
  54. package/dist/communications/contracts/newsletterWebhook.js +1 -0
  55. package/dist/communications/contracts/optOutContent.d.ts +11 -0
  56. package/dist/communications/contracts/optOutContent.js +17 -0
  57. package/dist/communications/contracts/orderCanceled.d.ts +25 -0
  58. package/dist/communications/contracts/orderCanceled.js +27 -0
  59. package/dist/communications/contracts/orderDraft.d.ts +46 -0
  60. package/dist/communications/contracts/orderDraft.js +46 -0
  61. package/dist/communications/contracts/orderPaid.d.ts +53 -0
  62. package/dist/communications/contracts/orderPaid.js +71 -0
  63. package/dist/communications/contracts/orderRefunded.d.ts +24 -0
  64. package/dist/communications/contracts/orderRefunded.js +27 -0
  65. package/dist/communications/contracts/paymentFailed.d.ts +24 -0
  66. package/dist/communications/contracts/paymentFailed.js +24 -0
  67. package/dist/communications/contracts/publicIntake.d.ts +49 -0
  68. package/dist/communications/contracts/publicIntake.js +1 -0
  69. package/dist/communications/contracts/render.d.ts +2 -0
  70. package/dist/communications/contracts/render.js +229 -0
  71. package/dist/communications/contracts/reorderReminder.d.ts +24 -0
  72. package/dist/communications/contracts/reorderReminder.js +24 -0
  73. package/dist/communications/contracts/returnEmails.d.ts +25 -0
  74. package/dist/communications/contracts/returnEmails.js +62 -0
  75. package/dist/communications/contracts/reviewEffects.d.ts +26 -0
  76. package/dist/communications/contracts/reviewEffects.js +25 -0
  77. package/dist/communications/contracts/reviewRequest.d.ts +26 -0
  78. package/dist/communications/contracts/reviewRequest.js +24 -0
  79. package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
  80. package/dist/communications/contracts/roleNotificationContent.js +46 -0
  81. package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
  82. package/dist/communications/contracts/shipmentDelivered.js +24 -0
  83. package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
  84. package/dist/communications/contracts/shipmentDispatched.js +35 -0
  85. package/dist/communications/contracts/shipmentException.d.ts +21 -0
  86. package/dist/communications/contracts/shipmentException.js +26 -0
  87. package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
  88. package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
  89. package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
  90. package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
  91. package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
  92. package/dist/communications/contracts/subscriptionCancelled.js +25 -0
  93. package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
  94. package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
  95. package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
  96. package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
  97. package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
  98. package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
  99. package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
  100. package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
  101. package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
  102. package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
  103. package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
  104. package/dist/communications/contracts/subscriptionPaused.js +25 -0
  105. package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
  106. package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
  107. package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
  108. package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
  109. package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
  110. package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
  111. package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
  112. package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
  113. package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
  114. package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
  115. package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
  116. package/dist/communications/contracts/subscriptionResumed.js +25 -0
  117. package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
  118. package/dist/communications/contracts/subscriptionWelcome.js +45 -0
  119. package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
  120. package/dist/communications/contracts/subscriptionWinback.js +28 -0
  121. package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
  122. package/dist/communications/contracts/transactionalDelivery.js +1 -0
  123. package/dist/communications/contracts/types.d.ts +46 -0
  124. package/dist/communications/contracts/types.js +1 -0
  125. package/dist/communications/contribution.d.ts +10 -0
  126. package/dist/communications/contribution.js +24 -0
  127. package/dist/communications/deliveryReconciliation.d.ts +64 -0
  128. package/dist/communications/deliveryReconciliation.js +60 -0
  129. package/dist/communications/emailEgress.d.ts +38 -0
  130. package/dist/communications/emailEgress.js +100 -0
  131. package/dist/communications/emailHealthAssessment.d.ts +39 -0
  132. package/dist/communications/emailHealthAssessment.js +59 -0
  133. package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
  134. package/dist/communications/emailHealthMetricsPort.js +1 -0
  135. package/dist/communications/emailNotificationControl.d.ts +27 -0
  136. package/dist/communications/emailNotificationControl.js +10 -0
  137. package/dist/communications/emailObservation.d.ts +28 -0
  138. package/dist/communications/emailObservation.js +23 -0
  139. package/dist/communications/emailTransport.d.ts +82 -0
  140. package/dist/communications/emailTransport.js +95 -0
  141. package/dist/communications/errors.d.ts +15 -0
  142. package/dist/communications/errors.js +30 -0
  143. package/dist/communications/index.d.ts +46 -0
  144. package/dist/communications/index.js +38 -0
  145. package/dist/communications/integrationEventSignature.d.ts +15 -0
  146. package/dist/communications/integrationEventSignature.js +37 -0
  147. package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
  148. package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
  149. package/dist/communications/nativeOperations.d.ts +79 -0
  150. package/dist/communications/nativeOperations.js +3 -0
  151. package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
  152. package/dist/communications/newsletterIntegrationContracts.js +128 -0
  153. package/dist/communications/newsletterWebhookPort.d.ts +11 -0
  154. package/dist/communications/newsletterWebhookPort.js +84 -0
  155. package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
  156. package/dist/communications/newsletterWebhookProcessing.js +71 -0
  157. package/dist/communications/originPolicy.d.ts +40 -0
  158. package/dist/communications/originPolicy.js +100 -0
  159. package/dist/communications/postgresCommunicationSync.d.ts +79 -0
  160. package/dist/communications/postgresCommunicationSync.js +111 -0
  161. package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
  162. package/dist/communications/postgresConsentEvaluator.js +43 -0
  163. package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
  164. package/dist/communications/postgresDeliveryReconciliation.js +113 -0
  165. package/dist/communications/postgresNativeOperations.d.ts +13 -0
  166. package/dist/communications/postgresNativeOperations.js +63 -0
  167. package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
  168. package/dist/communications/postgresNotificationRecipients.js +60 -0
  169. package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
  170. package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
  171. package/dist/communications/preferencesToken.d.ts +10 -0
  172. package/dist/communications/preferencesToken.js +40 -0
  173. package/dist/communications/publicPreferenceToken.d.ts +24 -0
  174. package/dist/communications/publicPreferenceToken.js +53 -0
  175. package/dist/communications/shipmentNotifications.d.ts +34 -0
  176. package/dist/communications/shipmentNotifications.js +61 -0
  177. package/dist/communications/sql.d.ts +26 -0
  178. package/dist/communications/sql.js +7 -0
  179. package/dist/communications/standardWebhooks.d.ts +14 -0
  180. package/dist/communications/standardWebhooks.js +73 -0
  181. package/dist/communications/status.d.ts +11 -0
  182. package/dist/communications/status.js +36 -0
  183. package/dist/communications/transactionalDelivery.d.ts +19 -0
  184. package/dist/communications/transactionalDelivery.js +96 -0
  185. package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
  186. package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
  187. package/dist/communications/unsubscribeToken.d.ts +77 -0
  188. package/dist/communications/unsubscribeToken.js +150 -0
  189. package/dist/communications/validation.d.ts +2 -0
  190. package/dist/communications/validation.js +3 -0
  191. package/dist/http/index.d.ts +1 -0
  192. package/dist/http/index.js +1 -0
  193. package/dist/http/webhookSignature.d.ts +42 -0
  194. package/dist/http/webhookSignature.js +86 -0
  195. package/dist/identity/adminAuthBinding.d.ts +28 -0
  196. package/dist/identity/adminAuthBinding.js +66 -0
  197. package/dist/identity/adminAuthorization.d.ts +34 -0
  198. package/dist/identity/adminAuthorization.js +72 -0
  199. package/dist/identity/adminContracts.d.ts +70 -0
  200. package/dist/identity/adminContracts.js +43 -0
  201. package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
  202. package/dist/identity/adminMagicLinkHandler.js +94 -0
  203. package/dist/identity/adminMeHandler.d.ts +15 -0
  204. package/dist/identity/adminMeHandler.js +49 -0
  205. package/dist/identity/adminPreflight.d.ts +16 -0
  206. package/dist/identity/adminPreflight.js +38 -0
  207. package/dist/identity/adminUserActionPorts.d.ts +31 -0
  208. package/dist/identity/adminUserActionPorts.js +51 -0
  209. package/dist/identity/adminUserHandlers.d.ts +38 -0
  210. package/dist/identity/adminUserHandlers.js +140 -0
  211. package/dist/identity/authVerifyUrl.d.ts +6 -0
  212. package/dist/identity/authVerifyUrl.js +54 -0
  213. package/dist/identity/contracts/contracts.d.ts +8 -0
  214. package/dist/identity/contracts/contracts.js +2 -0
  215. package/dist/identity/contracts/emailTrust.d.ts +3 -0
  216. package/dist/identity/contracts/emailTrust.js +4 -0
  217. package/dist/identity/contracts/index.d.ts +8 -0
  218. package/dist/identity/contracts/index.js +8 -0
  219. package/dist/identity/contracts/oidc.d.ts +3 -0
  220. package/dist/identity/contracts/oidc.js +9 -0
  221. package/dist/identity/contracts/operatorAuth.d.ts +35 -0
  222. package/dist/identity/contracts/operatorAuth.js +53 -0
  223. package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
  224. package/dist/identity/contracts/operatorInvitations.js +1 -0
  225. package/dist/identity/contracts/ports.d.ts +22 -0
  226. package/dist/identity/contracts/ports.js +1 -0
  227. package/dist/identity/contracts/privacy.d.ts +62 -0
  228. package/dist/identity/contracts/privacy.js +99 -0
  229. package/dist/identity/contracts/providerList.d.ts +3 -0
  230. package/dist/identity/contracts/providerList.js +11 -0
  231. package/dist/identity/contracts/types.d.ts +16 -0
  232. package/dist/identity/contracts/types.js +4 -0
  233. package/dist/identity/contribution.d.ts +8 -0
  234. package/dist/identity/contribution.js +22 -0
  235. package/dist/identity/customerAuth.d.ts +8 -0
  236. package/dist/identity/customerAuth.js +1 -0
  237. package/dist/identity/customerAuthorization.d.ts +5 -0
  238. package/dist/identity/customerAuthorization.js +22 -0
  239. package/dist/identity/customerContracts.d.ts +42 -0
  240. package/dist/identity/customerContracts.js +43 -0
  241. package/dist/identity/customerIdentityBinding.d.ts +44 -0
  242. package/dist/identity/customerIdentityBinding.js +60 -0
  243. package/dist/identity/customerIdentityVerifier.d.ts +8 -0
  244. package/dist/identity/customerIdentityVerifier.js +101 -0
  245. package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
  246. package/dist/identity/customerMagicLinkHandler.js +115 -0
  247. package/dist/identity/customerMeHandler.d.ts +11 -0
  248. package/dist/identity/customerMeHandler.js +46 -0
  249. package/dist/identity/customerPorts.d.ts +4 -0
  250. package/dist/identity/customerPorts.js +1 -0
  251. package/dist/identity/customerSessionBinding.d.ts +38 -0
  252. package/dist/identity/customerSessionBinding.js +80 -0
  253. package/dist/identity/customerSessionIssuer.d.ts +10 -0
  254. package/dist/identity/customerSessionIssuer.js +57 -0
  255. package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
  256. package/dist/identity/customerVerifyOtpHandler.js +41 -0
  257. package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
  258. package/dist/identity/directOperatorInvitationStore.js +193 -0
  259. package/dist/identity/emailTrustPolicy.d.ts +14 -0
  260. package/dist/identity/emailTrustPolicy.js +16 -0
  261. package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
  262. package/dist/identity/identityPersonalDataContributor.js +46 -0
  263. package/dist/identity/identityReadiness.d.ts +10 -0
  264. package/dist/identity/identityReadiness.js +25 -0
  265. package/dist/identity/index.d.ts +67 -0
  266. package/dist/identity/index.js +66 -0
  267. package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
  268. package/dist/identity/inviteAdminUserUseCase.js +75 -0
  269. package/dist/identity/jwtLibrary.d.ts +54 -0
  270. package/dist/identity/jwtLibrary.js +1 -0
  271. package/dist/identity/machineActorWriteFence.d.ts +4 -0
  272. package/dist/identity/machineActorWriteFence.js +13 -0
  273. package/dist/identity/magicLinkPorts.d.ts +28 -0
  274. package/dist/identity/magicLinkPorts.js +1 -0
  275. package/dist/identity/managedAccountLinkStore.d.ts +2 -0
  276. package/dist/identity/managedAccountLinkStore.js +93 -0
  277. package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
  278. package/dist/identity/managedAdminAuthVerifier.js +27 -0
  279. package/dist/identity/managedAdminUserGateways.d.ts +123 -0
  280. package/dist/identity/managedAdminUserGateways.js +127 -0
  281. package/dist/identity/managedClient.d.ts +90 -0
  282. package/dist/identity/managedClient.js +10 -0
  283. package/dist/identity/managedClientAuthLink.d.ts +3 -0
  284. package/dist/identity/managedClientAuthLink.js +115 -0
  285. package/dist/identity/managedCustomerMe.d.ts +13 -0
  286. package/dist/identity/managedCustomerMe.js +30 -0
  287. package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
  288. package/dist/identity/managedEmailTrustPolicy.js +105 -0
  289. package/dist/identity/managedIdentityVerifier.d.ts +2 -0
  290. package/dist/identity/managedIdentityVerifier.js +16 -0
  291. package/dist/identity/managedPrivacy.d.ts +4 -0
  292. package/dist/identity/managedPrivacy.js +39 -0
  293. package/dist/identity/messages.d.ts +29 -0
  294. package/dist/identity/messages.js +39 -0
  295. package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
  296. package/dist/identity/oidcIdentityVerifier.js +309 -0
  297. package/dist/identity/oidcPrincipal.d.ts +2 -0
  298. package/dist/identity/oidcPrincipal.js +10 -0
  299. package/dist/identity/operatorAdminAuth.d.ts +6 -0
  300. package/dist/identity/operatorAdminAuth.js +13 -0
  301. package/dist/identity/operatorAuthHandlers.d.ts +18 -0
  302. package/dist/identity/operatorAuthHandlers.js +71 -0
  303. package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
  304. package/dist/identity/operatorIdentityVerifier.js +19 -0
  305. package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
  306. package/dist/identity/operatorInvitationLifecycle.js +15 -0
  307. package/dist/identity/operatorInvitationPorts.d.ts +31 -0
  308. package/dist/identity/operatorInvitationPorts.js +1 -0
  309. package/dist/identity/operatorPorts.d.ts +37 -0
  310. package/dist/identity/operatorPorts.js +1 -0
  311. package/dist/identity/operatorSession.d.ts +13 -0
  312. package/dist/identity/operatorSession.js +31 -0
  313. package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
  314. package/dist/identity/operatorTokenAdminAuth.js +50 -0
  315. package/dist/identity/personalDataRegistry.d.ts +4 -0
  316. package/dist/identity/personalDataRegistry.js +11 -0
  317. package/dist/identity/platformAdminAuth.d.ts +15 -0
  318. package/dist/identity/platformAdminAuth.js +1 -0
  319. package/dist/identity/ports.d.ts +127 -0
  320. package/dist/identity/ports.js +38 -0
  321. package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
  322. package/dist/identity/postgresAccountLinkStore.js +52 -0
  323. package/dist/identity/postgresCustomerMe.d.ts +3 -0
  324. package/dist/identity/postgresCustomerMe.js +21 -0
  325. package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
  326. package/dist/identity/postgresCustomerRecovery.js +57 -0
  327. package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
  328. package/dist/identity/postgresOperatorIdentity.js +137 -0
  329. package/dist/identity/postgresPrivacy.d.ts +7 -0
  330. package/dist/identity/postgresPrivacy.js +126 -0
  331. package/dist/identity/privacyHandlers.d.ts +10 -0
  332. package/dist/identity/privacyHandlers.js +35 -0
  333. package/dist/identity/privacyPorts.d.ts +72 -0
  334. package/dist/identity/privacyPorts.js +8 -0
  335. package/dist/identity/privacyUseCases.d.ts +5 -0
  336. package/dist/identity/privacyUseCases.js +55 -0
  337. package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
  338. package/dist/identity/reconcileAccountForPrincipal.js +66 -0
  339. package/dist/identity/reconcileAccountHandler.d.ts +11 -0
  340. package/dist/identity/reconcileAccountHandler.js +30 -0
  341. package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
  342. package/dist/identity/removeAdminUserUseCase.js +60 -0
  343. package/dist/identity/response.d.ts +3 -0
  344. package/dist/identity/response.js +2 -0
  345. package/dist/identity/sql.d.ts +44 -0
  346. package/dist/identity/sql.js +10 -0
  347. package/package.json +22 -2
  348. package/release-gates.json +63 -8
  349. package/sql/20260807190000_comms_outbox_rail.sql +329 -0
  350. package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
  351. package/sql/20260810180001_self_host_actor_auth.sql +40 -0
  352. package/sql/20260810204057_customer_identity_read.sql +31 -0
  353. package/sql/20260813120000_communications_control_plane.sql +568 -0
  354. package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
  355. package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
  356. package/sql/20261018120000_identity_operator_invitations.sql +42 -0
  357. package/sql/20261018180000_identity_operator_challenges.sql +56 -0
  358. package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
  359. package/sql/20261019200000_communications_native_operations.sql +68 -0
  360. package/src/communications/accountingDocumentDelivery.ts +63 -0
  361. package/src/communications/authEmailError.ts +10 -0
  362. package/src/communications/authEmailPorts.ts +2 -0
  363. package/src/communications/authSendEmailHook.ts +310 -0
  364. package/src/communications/capturedTransactionalDelivery.ts +29 -0
  365. package/src/communications/communicationSync.ts +96 -0
  366. package/src/communications/communicationSyncWorker.ts +302 -0
  367. package/src/communications/consentPolicy.ts +82 -0
  368. package/src/communications/consentPurposeRegistry.ts +31 -0
  369. package/src/communications/contracts/abandonedCart.ts +64 -0
  370. package/src/communications/contracts/authEmail.ts +73 -0
  371. package/src/communications/contracts/backInStock.ts +69 -0
  372. package/src/communications/contracts/blocks.ts +47 -0
  373. package/src/communications/contracts/checkoutExpired.ts +62 -0
  374. package/src/communications/contracts/checkoutRecovery.ts +68 -0
  375. package/src/communications/contracts/commerceEmailContent.ts +189 -0
  376. package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
  377. package/src/communications/contracts/consent.ts +13 -0
  378. package/src/communications/contracts/email.ts +210 -0
  379. package/src/communications/contracts/guideCard.ts +34 -0
  380. package/src/communications/contracts/index.ts +51 -0
  381. package/src/communications/contracts/inquiryContent.ts +53 -0
  382. package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
  383. package/src/communications/contracts/marketingAutomation.ts +45 -0
  384. package/src/communications/contracts/newsletter.ts +78 -0
  385. package/src/communications/contracts/newsletterWebhook.ts +23 -0
  386. package/src/communications/contracts/optOutContent.ts +23 -0
  387. package/src/communications/contracts/orderCanceled.ts +64 -0
  388. package/src/communications/contracts/orderDraft.ts +112 -0
  389. package/src/communications/contracts/orderPaid.ts +147 -0
  390. package/src/communications/contracts/orderRefunded.ts +63 -0
  391. package/src/communications/contracts/paymentFailed.ts +59 -0
  392. package/src/communications/contracts/publicIntake.ts +53 -0
  393. package/src/communications/contracts/render.ts +271 -0
  394. package/src/communications/contracts/reorderReminder.ts +59 -0
  395. package/src/communications/contracts/returnEmails.ts +113 -0
  396. package/src/communications/contracts/reviewEffects.ts +60 -0
  397. package/src/communications/contracts/reviewRequest.ts +59 -0
  398. package/src/communications/contracts/roleNotificationContent.ts +65 -0
  399. package/src/communications/contracts/shipmentDelivered.ts +59 -0
  400. package/src/communications/contracts/shipmentDispatched.ts +77 -0
  401. package/src/communications/contracts/shipmentException.ts +62 -0
  402. package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
  403. package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
  404. package/src/communications/contracts/subscriptionCancelled.ts +57 -0
  405. package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
  406. package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
  407. package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
  408. package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
  409. package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
  410. package/src/communications/contracts/subscriptionPaused.ts +58 -0
  411. package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
  412. package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
  413. package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
  414. package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
  415. package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
  416. package/src/communications/contracts/subscriptionResumed.ts +60 -0
  417. package/src/communications/contracts/subscriptionWelcome.ts +102 -0
  418. package/src/communications/contracts/subscriptionWinback.ts +62 -0
  419. package/src/communications/contracts/transactionalDelivery.ts +43 -0
  420. package/src/communications/contracts/types.ts +58 -0
  421. package/src/communications/contribution.ts +33 -0
  422. package/src/communications/deliveryReconciliation.ts +108 -0
  423. package/src/communications/emailEgress.ts +111 -0
  424. package/src/communications/emailHealthAssessment.ts +106 -0
  425. package/src/communications/emailHealthMetricsPort.ts +17 -0
  426. package/src/communications/emailNotificationControl.ts +28 -0
  427. package/src/communications/emailObservation.ts +43 -0
  428. package/src/communications/emailTransport.ts +167 -0
  429. package/src/communications/errors.ts +34 -0
  430. package/src/communications/index.ts +46 -0
  431. package/src/communications/integrationEventSignature.ts +49 -0
  432. package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
  433. package/src/communications/nativeOperations.ts +40 -0
  434. package/src/communications/newsletterIntegrationContracts.ts +154 -0
  435. package/src/communications/newsletterWebhookPort.ts +104 -0
  436. package/src/communications/newsletterWebhookProcessing.ts +102 -0
  437. package/src/communications/originPolicy.ts +151 -0
  438. package/src/communications/postgresCommunicationSync.ts +127 -0
  439. package/src/communications/postgresConsentEvaluator.ts +49 -0
  440. package/src/communications/postgresDeliveryReconciliation.ts +101 -0
  441. package/src/communications/postgresNativeOperations.ts +55 -0
  442. package/src/communications/postgresNotificationRecipients.ts +57 -0
  443. package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
  444. package/src/communications/preferencesToken.ts +62 -0
  445. package/src/communications/publicPreferenceToken.ts +68 -0
  446. package/src/communications/shipmentNotifications.ts +78 -0
  447. package/src/communications/sql.ts +7 -0
  448. package/src/communications/standardWebhooks.ts +84 -0
  449. package/src/communications/status.ts +51 -0
  450. package/src/communications/transactionalDelivery.ts +114 -0
  451. package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
  452. package/src/communications/unsubscribeToken.ts +186 -0
  453. package/src/communications/validation.ts +3 -0
  454. package/src/http/index.ts +1 -0
  455. package/src/http/webhookSignature.ts +96 -0
  456. package/src/identity/adminAuthBinding.ts +76 -0
  457. package/src/identity/adminAuthorization.ts +106 -0
  458. package/src/identity/adminContracts.ts +52 -0
  459. package/src/identity/adminMagicLinkHandler.ts +132 -0
  460. package/src/identity/adminMeHandler.ts +64 -0
  461. package/src/identity/adminPreflight.ts +46 -0
  462. package/src/identity/adminUserActionPorts.ts +78 -0
  463. package/src/identity/adminUserHandlers.ts +174 -0
  464. package/src/identity/authVerifyUrl.ts +56 -0
  465. package/src/identity/contracts/contracts.ts +15 -0
  466. package/src/identity/contracts/emailTrust.ts +5 -0
  467. package/src/identity/contracts/index.ts +8 -0
  468. package/src/identity/contracts/oidc.ts +10 -0
  469. package/src/identity/contracts/operatorAuth.ts +61 -0
  470. package/src/identity/contracts/operatorInvitations.ts +32 -0
  471. package/src/identity/contracts/ports.ts +25 -0
  472. package/src/identity/contracts/privacy.ts +86 -0
  473. package/src/identity/contracts/providerList.ts +13 -0
  474. package/src/identity/contracts/types.ts +25 -0
  475. package/src/identity/contribution.ts +29 -0
  476. package/src/identity/customerAuth.ts +3 -0
  477. package/src/identity/customerAuthorization.ts +38 -0
  478. package/src/identity/customerContracts.ts +52 -0
  479. package/src/identity/customerIdentityBinding.ts +125 -0
  480. package/src/identity/customerIdentityVerifier.ts +141 -0
  481. package/src/identity/customerMagicLinkHandler.ts +171 -0
  482. package/src/identity/customerMeHandler.ts +64 -0
  483. package/src/identity/customerPorts.ts +2 -0
  484. package/src/identity/customerSessionBinding.ts +114 -0
  485. package/src/identity/customerSessionIssuer.ts +71 -0
  486. package/src/identity/customerVerifyOtpHandler.ts +44 -0
  487. package/src/identity/directOperatorInvitationStore.ts +178 -0
  488. package/src/identity/emailTrustPolicy.ts +16 -0
  489. package/src/identity/identityPersonalDataContributor.ts +48 -0
  490. package/src/identity/identityReadiness.ts +26 -0
  491. package/src/identity/index.ts +69 -0
  492. package/src/identity/inviteAdminUserUseCase.ts +134 -0
  493. package/src/identity/jwtLibrary.ts +24 -0
  494. package/src/identity/machineActorWriteFence.ts +19 -0
  495. package/src/identity/magicLinkPorts.ts +7 -0
  496. package/src/identity/managedAccountLinkStore.ts +105 -0
  497. package/src/identity/managedAdminAuthVerifier.ts +40 -0
  498. package/src/identity/managedAdminUserGateways.ts +164 -0
  499. package/src/identity/managedClient.ts +58 -0
  500. package/src/identity/managedClientAuthLink.ts +120 -0
  501. package/src/identity/managedCustomerMe.ts +52 -0
  502. package/src/identity/managedEmailTrustPolicy.ts +90 -0
  503. package/src/identity/managedIdentityVerifier.ts +20 -0
  504. package/src/identity/managedPrivacy.ts +37 -0
  505. package/src/identity/messages.ts +41 -0
  506. package/src/identity/oidcIdentityVerifier.ts +229 -0
  507. package/src/identity/oidcPrincipal.ts +10 -0
  508. package/src/identity/operatorAdminAuth.ts +14 -0
  509. package/src/identity/operatorAuthHandlers.ts +52 -0
  510. package/src/identity/operatorIdentityVerifier.ts +19 -0
  511. package/src/identity/operatorInvitationLifecycle.ts +14 -0
  512. package/src/identity/operatorInvitationPorts.ts +11 -0
  513. package/src/identity/operatorPorts.ts +11 -0
  514. package/src/identity/operatorSession.ts +30 -0
  515. package/src/identity/operatorTokenAdminAuth.ts +79 -0
  516. package/src/identity/personalDataRegistry.ts +8 -0
  517. package/src/identity/platformAdminAuth.ts +7 -0
  518. package/src/identity/ports.ts +158 -0
  519. package/src/identity/postgresAccountLinkStore.ts +49 -0
  520. package/src/identity/postgresCustomerMe.ts +33 -0
  521. package/src/identity/postgresCustomerRecovery.ts +72 -0
  522. package/src/identity/postgresOperatorIdentity.ts +123 -0
  523. package/src/identity/postgresPrivacy.ts +117 -0
  524. package/src/identity/privacyHandlers.ts +33 -0
  525. package/src/identity/privacyPorts.ts +52 -0
  526. package/src/identity/privacyUseCases.ts +48 -0
  527. package/src/identity/reconcileAccountForPrincipal.ts +99 -0
  528. package/src/identity/reconcileAccountHandler.ts +36 -0
  529. package/src/identity/removeAdminUserUseCase.ts +85 -0
  530. package/src/identity/response.ts +3 -0
  531. package/src/identity/sql.ts +10 -0
@@ -0,0 +1,137 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createPostgresRoleFreeTransactionLane } from "../data/index.js";
3
+ import { acceptDirectOperatorInvitationInTransaction } from "./directOperatorInvitationStore.js";
4
+ import { operatorChallengeRequestSchema } from "./contracts/operatorAuth.js";
5
+ const fingerprint = (email) => createHash("sha256").update(email.trim().toLowerCase()).digest("hex");
6
+ const allowed = (role) => role === "admin" || role === "distributor";
7
+ /** Dedicated owner lane: no actor gateway, customer writes or machine grants. */
8
+ export function createPostgresOperatorIdentityStore(env) {
9
+ const lane = createPostgresRoleFreeTransactionLane(env);
10
+ const run = (work) => lane.run(db => work(db));
11
+ async function safeStore(db) {
12
+ // Includes privileges inherited via memberships/default ACLs, not just explicit grants.
13
+ const unsafe = (await db.query(`SELECT EXISTS (
14
+ SELECT 1 FROM pg_roles r WHERE r.rolname IN ('anon', 'authenticated', 'authenticator')
15
+ AND (has_table_privilege(r.oid, 'public.operator_identity_challenges', 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER')
16
+ OR has_any_column_privilege(r.oid, 'public.operator_identity_challenges', 'SELECT,INSERT,UPDATE,REFERENCES'))
17
+ ) OR EXISTS (SELECT 1 FROM pg_class c, LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) a
18
+ WHERE c.oid = 'public.operator_identity_challenges'::regclass AND a.grantee = 0)
19
+ OR EXISTS (SELECT 1 FROM pg_attribute c, LATERAL aclexplode(c.attacl) a
20
+ WHERE c.attrelid = 'public.operator_identity_challenges'::regclass AND a.grantee = 0) AS unsafe`)).rows[0]?.unsafe;
21
+ if (unsafe !== false)
22
+ throw new Error("operator_challenge_store_privileges_unsafe");
23
+ }
24
+ async function lockPrincipal(db, email) {
25
+ // Same serialization key as the invitation lifecycle, before row locks.
26
+ await db.query("SELECT pg_advisory_xact_lock(hashtext($1)::bigint)", [`operator_invitation:${email}`]);
27
+ const matches = (await db.query("SELECT principal_id, email FROM public.platform_human_principals WHERE lower(btrim(email)) = $1 FOR UPDATE", [email])).rows;
28
+ if (matches.length > 1)
29
+ throw new Error("operator_normalized_email_conflict");
30
+ return matches[0] ?? null;
31
+ }
32
+ async function eligible(db, id, email) {
33
+ if ((await db.query("SELECT 1 FROM public.platform_control_operators WHERE principal_id = $1", [id])).rows.length)
34
+ return false;
35
+ const member = (await db.query("SELECT role, membership_state FROM public.platform_human_memberships WHERE principal_id = $1 FOR UPDATE", [id])).rows[0];
36
+ if (member?.membership_state === "active")
37
+ return allowed(member.role);
38
+ const pending = (await db.query(`SELECT role FROM public.identity_operator_invitations WHERE principal_id = $1
39
+ AND email_key = $2 AND phase = 'invited' AND accepted_at IS NULL AND closed_at IS NULL
40
+ AND expires_at > clock_timestamp() FOR UPDATE`, [id, email])).rows[0];
41
+ return !!pending && allowed(pending.role);
42
+ }
43
+ return {
44
+ issue({ email, tokenHash }) {
45
+ return run(async (db) => {
46
+ await safeStore(db);
47
+ const principal = await lockPrincipal(db, email);
48
+ if (!principal || !await eligible(db, String(principal.principal_id), email))
49
+ return { deliverable: false };
50
+ const stamp = (await db.query("SELECT clock_timestamp() AS at")).rows[0].at;
51
+ const counts = (await db.query(`SELECT count(*) AS hour_count,
52
+ count(*) FILTER (WHERE created_at > $2::timestamptz - interval '60 seconds') AS minute_count
53
+ FROM public.operator_identity_challenges WHERE principal_id = $1 AND created_at > $2::timestamptz - interval '1 hour'`, [principal.principal_id, stamp])).rows[0];
54
+ if (Number(counts.minute_count) >= 1 || Number(counts.hour_count) >= 5)
55
+ return { deliverable: false };
56
+ const row = (await db.query(`INSERT INTO public.operator_identity_challenges
57
+ (principal_id, email_fingerprint, token_hash, created_at, expires_at)
58
+ VALUES ($1, $2, $3, $4, $4::timestamptz + interval '10 minutes') RETURNING expires_at`, [principal.principal_id, fingerprint(email), tokenHash, stamp])).rows[0];
59
+ return { deliverable: true, email: String(principal.email).trim().toLowerCase(), expiresAt: new Date(String(row.expires_at)).toISOString() };
60
+ });
61
+ },
62
+ redeem({ tokenHash }) {
63
+ return run(async (db) => {
64
+ await safeStore(db);
65
+ const candidate = (await db.query(`SELECT p.email, p.principal_id FROM public.operator_identity_challenges c
66
+ JOIN public.platform_human_principals p USING (principal_id) WHERE c.token_hash = $1`, [tokenHash])).rows[0];
67
+ if (!candidate)
68
+ return null;
69
+ const email = String(candidate.email).trim().toLowerCase();
70
+ const principal = await lockPrincipal(db, email);
71
+ if (!principal || principal.principal_id !== candidate.principal_id || !await eligible(db, String(principal.principal_id), email))
72
+ return null;
73
+ const challenge = (await db.query(`SELECT * FROM public.operator_identity_challenges WHERE token_hash = $1
74
+ AND principal_id = $2 AND used_at IS NULL AND expires_at > clock_timestamp() FOR UPDATE`, [tokenHash, principal.principal_id])).rows[0];
75
+ if (!challenge || challenge.email_fingerprint !== fingerprint(email))
76
+ return null;
77
+ const accepted = await acceptDirectOperatorInvitationInTransaction(db, String(principal.principal_id));
78
+ if ((accepted.code !== "ACTIVATED" && accepted.code !== "ALREADY_ACTIVE") || !allowed(accepted.role))
79
+ return null;
80
+ const consumed = await db.query(`WITH stamp AS (SELECT clock_timestamp() AS at)
81
+ UPDATE public.operator_identity_challenges SET used_at = stamp.at FROM stamp
82
+ WHERE id = $1 AND used_at IS NULL AND expires_at > stamp.at RETURNING id`, [challenge.id]);
83
+ // Throw to roll activation back if the challenge expired during activation.
84
+ if (!consumed.rows.length)
85
+ throw new Error("operator_challenge_expired_during_activation");
86
+ return { principalId: String(principal.principal_id), email };
87
+ }).catch(error => {
88
+ if (error instanceof Error && error.message === "operator_challenge_expired_during_activation")
89
+ return null;
90
+ throw error;
91
+ });
92
+ },
93
+ findMembership(id) {
94
+ return run(async (db) => {
95
+ await safeStore(db);
96
+ const row = (await db.query(`SELECT p.principal_id, p.email, m.role, m.membership_state
97
+ FROM public.platform_human_principals p JOIN public.platform_human_memberships m USING (principal_id)
98
+ WHERE p.principal_id = $1 AND NOT EXISTS (SELECT 1 FROM public.platform_control_operators o WHERE o.principal_id = p.principal_id)`, [id])).rows[0];
99
+ return row ? { principalId: String(row.principal_id), email: String(row.email), role: row.role, membershipState: String(row.membership_state) } : null;
100
+ });
101
+ },
102
+ bootstrapFirstAdministrator(input) {
103
+ const { email } = operatorChallengeRequestSchema.parse(input);
104
+ return run(async (db) => {
105
+ await safeStore(db);
106
+ await db.query("LOCK TABLE public.platform_human_memberships, public.identity_operator_invitations IN SHARE ROW EXCLUSIVE MODE");
107
+ if ((await db.query(`SELECT 1 FROM public.platform_human_memberships UNION ALL SELECT 1 FROM public.identity_operator_invitations LIMIT 1`)).rows.length)
108
+ throw new Error("OPERATOR_BOOTSTRAP_ALREADY_INITIALIZED");
109
+ let principal = await lockPrincipal(db, email);
110
+ if (!principal)
111
+ principal = (await db.query("INSERT INTO public.platform_human_principals (principal_id, email) VALUES (gen_random_uuid(), $1) RETURNING principal_id, email", [email])).rows[0];
112
+ const id = String(principal.principal_id);
113
+ if ((await db.query("SELECT 1 FROM public.platform_control_operators WHERE principal_id = $1", [id])).rows.length)
114
+ throw new Error("operator_bootstrap_machine_refused");
115
+ const pending = (await db.query(`WITH stamp AS (SELECT clock_timestamp() AS at)
116
+ INSERT INTO public.identity_operator_invitations
117
+ (principal_id, invitation_id, email_key, role, phase, invited_by, invited_at, expires_at)
118
+ SELECT $1, gen_random_uuid(), $2, 'admin', 'invited', $1, at, at + interval '7 days' FROM stamp RETURNING *`, [id, email])).rows[0];
119
+ await db.query(`INSERT INTO public.identity_operator_invitation_events
120
+ (event_id, invitation_id, principal_id, action, actor_kind, old_value, new_value, occurred_at)
121
+ VALUES (gen_random_uuid(), $1, $2, 'issue', 'system', '{}'::jsonb, $3::jsonb, clock_timestamp())`, [pending.invitation_id, id, JSON.stringify(pending)]);
122
+ return { principalId: id, membershipState: "invited" };
123
+ });
124
+ },
125
+ pruneChallenges({ limit }) {
126
+ if (!Number.isInteger(limit) || limit < 1 || limit > 500)
127
+ throw new Error("operator_prune_limit_invalid");
128
+ return run(async (db) => {
129
+ const deleted = await db.query(`WITH batch AS (SELECT id FROM public.operator_identity_challenges
130
+ WHERE expires_at < clock_timestamp() - interval '24 hours' ORDER BY expires_at, id LIMIT $1 FOR UPDATE SKIP LOCKED)
131
+ DELETE FROM public.operator_identity_challenges c USING batch WHERE c.id = batch.id RETURNING c.id`, [limit]);
132
+ return { deleted: deleted.rows.length };
133
+ });
134
+ },
135
+ close: lane.close,
136
+ };
137
+ }
@@ -0,0 +1,7 @@
1
+ import { type PgQueryExecutor, type PostgresDataGatewayEnv, type PostgresDataGatewayOptions } from "../data/index.js";
2
+ import { type PrivacyStore } from "./privacyPorts.js";
3
+ export declare function createPostgresPrivacyStore(): PrivacyStore<PgQueryExecutor>;
4
+ export declare function createPostgresPrivacyUnitOfWork(env: PostgresDataGatewayEnv, options?: PostgresDataGatewayOptions): {
5
+ run: <T>(work: (context: PgQueryExecutor) => Promise<T>) => Promise<T>;
6
+ close: () => Promise<void>;
7
+ };
@@ -0,0 +1,126 @@
1
+ import { createPostgresRoleFreeTransactionLane } from "../data/index.js";
2
+ import { PrivacyRefusal } from "./privacyPorts.js";
3
+ import { erasureReceiptSchema } from "./contracts/privacy.js";
4
+ function json(value) { return JSON.parse(JSON.stringify(value)); }
5
+ export function createPostgresPrivacyStore() {
6
+ return {
7
+ erasureSupported: true,
8
+ async prepareErasure(db) {
9
+ await db.query("SET LOCAL lock_timeout = '5s'");
10
+ await db.query("SET LOCAL statement_timeout = '15s'");
11
+ // Serialize receipt/key decisions; challenge relation precedes every contact lock.
12
+ await db.query("LOCK TABLE public.identity_privacy_erasures IN SHARE ROW EXCLUSIVE MODE");
13
+ const unsafe = (await db.query(`SELECT EXISTS (
14
+ SELECT 1 FROM pg_roles r WHERE r.rolname IN ('anon', 'authenticated', 'authenticator')
15
+ AND (has_table_privilege(r.oid, 'public.identity_privacy_erasures', 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER')
16
+ OR has_any_column_privilege(r.oid, 'public.identity_privacy_erasures', 'SELECT,INSERT,UPDATE,REFERENCES'))
17
+ ) OR EXISTS (SELECT 1 FROM pg_class c, LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) a
18
+ WHERE c.oid = 'public.identity_privacy_erasures'::regclass AND a.grantee = 0)
19
+ OR EXISTS (SELECT 1 FROM pg_attribute c, LATERAL aclexplode(c.attacl) a
20
+ WHERE c.attrelid = 'public.identity_privacy_erasures'::regclass AND a.grantee = 0) AS unsafe`)).rows[0]?.unsafe;
21
+ if (unsafe !== false)
22
+ throw new PrivacyRefusal("PRIVACY_STORE_PRIVILEGES_UNSAFE");
23
+ await db.query("LOCK TABLE public.customer_identity_challenges IN EXCLUSIVE MODE");
24
+ },
25
+ async authorizeErasure(db, actor) {
26
+ const row = (await db.query(`SELECT m.role, m.membership_state FROM public.platform_human_memberships m
27
+ WHERE m.principal_id = $1 AND NOT EXISTS (SELECT 1 FROM public.platform_control_operators o WHERE o.principal_id = m.principal_id)`, [actor.principalId])).rows[0];
28
+ if (row?.role !== "admin" || row.membership_state !== "active")
29
+ throw new PrivacyRefusal("PRIVACY_PROTECTED_OPERATOR");
30
+ },
31
+ async resolve(db, ref, actor) {
32
+ const contactPredicate = ref.kind === "contact" ? "c.id = $1" : "c.principal_id = $1";
33
+ const id = ref.kind === "contact" ? ref.contactId : ref.principalId;
34
+ let contact = (await db.query(`SELECT c.* FROM public.clients c WHERE ${contactPredicate}`, [id])).rows[0] ?? null;
35
+ if (ref.kind === "principal" && !contact) {
36
+ const receipt = (await db.query("SELECT client_id FROM public.identity_privacy_erasures WHERE principal_id = $1", [id])).rows[0];
37
+ if (receipt?.client_id)
38
+ contact = (await db.query("SELECT * FROM public.clients WHERE id = $1", [receipt.client_id])).rows[0] ?? null;
39
+ }
40
+ let principalId = ref.kind === "principal" ? ref.principalId : contact?.principal_id ? String(contact.principal_id) : null;
41
+ if (!principalId && contact) {
42
+ const receipt = (await db.query("SELECT principal_id FROM public.identity_privacy_erasures WHERE client_id = $1", [contact.id])).rows[0];
43
+ if (receipt?.principal_id)
44
+ principalId = String(receipt.principal_id);
45
+ }
46
+ let principal = principalId ? (await db.query("SELECT principal_id, email, created_at FROM public.platform_human_principals WHERE principal_id = $1", [principalId])).rows[0] ?? null : null;
47
+ if (!contact && !principal)
48
+ return null;
49
+ if (actor) {
50
+ if (principal) {
51
+ await db.query("SELECT pg_advisory_xact_lock(hashtext($1)::bigint)", [`operator_invitation:${String(principal.email).trim().toLowerCase()}`]);
52
+ }
53
+ await db.query("SELECT principal_id FROM public.platform_human_memberships WHERE principal_id = ANY($1::uuid[]) ORDER BY principal_id FOR UPDATE", [[actor.principalId, principalId].filter(Boolean)]);
54
+ if (contact)
55
+ contact = (await db.query("SELECT * FROM public.clients WHERE id = $1 FOR UPDATE", [contact.id])).rows[0] ?? null;
56
+ if (principal)
57
+ principal = (await db.query("SELECT principal_id, email, created_at FROM public.platform_human_principals WHERE principal_id = $1 FOR UPDATE", [principalId])).rows[0] ?? null;
58
+ }
59
+ const membership = principalId ? (await db.query("SELECT * FROM public.platform_human_memberships WHERE principal_id = $1", [principalId])).rows[0] ?? null : null;
60
+ const invitations = principalId ? (await db.query("SELECT * FROM public.identity_operator_invitations WHERE principal_id = $1", [principalId])).rows : [];
61
+ const invitationHistory = principalId ? (await db.query("SELECT * FROM public.identity_operator_invitation_events WHERE principal_id = $1 ORDER BY occurred_at, event_id", [principalId])).rows : [];
62
+ const membershipHistory = principalId ? (await db.query("SELECT * FROM public.platform_human_membership_audit_events WHERE target_principal_id = $1 ORDER BY occurred_at, id", [principalId])).rows : [];
63
+ return { contactId: contact ? String(contact.id) : null, principalId,
64
+ identity: { contact: json(contact), principal: json(principal), membership: json(membership), invitations: json(invitations), invitationHistory: json(invitationHistory), membershipHistory: json(membershipHistory) } };
65
+ },
66
+ async findReceipt(db, ref, key) {
67
+ const column = ref.kind === "contact" ? "client_id" : "principal_id";
68
+ const id = ref.kind === "contact" ? ref.contactId : ref.principalId;
69
+ const rows = (await db.query(`SELECT * FROM public.identity_privacy_erasures WHERE ${column} = $1 OR idempotency_key = $2::uuid`, [id, key ?? null])).rows;
70
+ if (rows.some(row => row.idempotency_key === key && row[column] !== id))
71
+ throw new PrivacyRefusal("PRIVACY_IDEMPOTENCY_CONFLICT");
72
+ const row = rows.find(row => row[column] === id);
73
+ return row ? erasureReceiptSchema.parse({ version: row.contract_version, erasureId: row.erasure_id, completedAt: row.completed_at instanceof Date ? row.completed_at.toISOString() : new Date(String(row.completed_at)).toISOString(), outcome: "ERASED", obligations: row.retained_obligations }) : null;
74
+ },
75
+ async eraseIdentity(db, subject, actor, pseudonym) {
76
+ await erasePostgresIdentity(db, subject, actor, pseudonym);
77
+ },
78
+ async saveReceipt(db, subject, actor, key, receipt, dispositions) {
79
+ await db.query(`INSERT INTO public.identity_privacy_erasures
80
+ (erasure_id, idempotency_key, client_id, principal_id, actor_principal_id, contract_version, contributor_versions, dispositions, retained_obligations, completed_at)
81
+ VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb,$9::jsonb,$10::timestamptz)`, [receipt.erasureId, key, subject.contactId, subject.principalId, actor.principalId, receipt.version,
82
+ JSON.stringify(Object.fromEntries(dispositions.map(d => [d.contributorId, d.version]))), JSON.stringify(dispositions), JSON.stringify(receipt.obligations), receipt.completedAt]);
83
+ },
84
+ };
85
+ }
86
+ async function erasePostgresIdentity(db, subject, actor, pseudonym) {
87
+ const id = subject.principalId;
88
+ if (id) {
89
+ if (id === actor.principalId || (await db.query("SELECT 1 FROM public.platform_control_operators WHERE principal_id = $1", [id])).rows.length)
90
+ throw new PrivacyRefusal("PRIVACY_PROTECTED_OPERATOR");
91
+ const member = subject.identity.membership;
92
+ if (member?.membership_state === "active") {
93
+ try {
94
+ await db.query("SELECT public.platform_revoke_human_membership($1, $2, $3)", [actor.principalId, id, "identity erasure"]);
95
+ }
96
+ catch (error) {
97
+ if (error && typeof error === "object" && "code" in error && ["P0001", "42501"].includes(String(error.code)))
98
+ throw new PrivacyRefusal("PRIVACY_PROTECTED_OPERATOR");
99
+ throw error;
100
+ }
101
+ }
102
+ await db.query("DELETE FROM public.operator_identity_challenges WHERE principal_id = $1", [id]);
103
+ await db.query("DELETE FROM public.identity_operator_invitations WHERE principal_id = $1 AND phase <> 'accepted'", [id]);
104
+ await db.query("UPDATE public.identity_operator_invitations SET email_key = $2 WHERE principal_id = $1", [id, pseudonym]);
105
+ // Rebuild historical identity snapshots from lifecycle fields, never a recursive string replacement.
106
+ await db.query(`UPDATE public.identity_operator_invitation_events SET
107
+ old_value = old_value - 'email_key', new_value = new_value - 'email_key' WHERE principal_id = $1`, [id]);
108
+ await db.query(`UPDATE public.platform_human_membership_audit_events SET reason = NULL,
109
+ old_value = old_value - 'revocationReason', new_value = new_value - 'revocationReason' WHERE target_principal_id = $1`, [id]);
110
+ // The existing lifecycle guard also protects free-text redaction on a revoked row.
111
+ const previous = (await db.query("SELECT current_setting('app.platform_human_membership_revoke', true) AS previous")).rows[0]?.previous;
112
+ await db.query("SELECT set_config('app.platform_human_membership_revoke', 'true', true)");
113
+ await db.query("UPDATE public.platform_human_memberships SET membership_revocation_reason = NULL WHERE principal_id = $1 AND membership_state = 'revoked'", [id]);
114
+ await db.query("SELECT set_config('app.platform_human_membership_revoke', $1, true)", [previous ?? ""]);
115
+ await db.query("UPDATE public.platform_human_principals SET email = $2 WHERE principal_id = $1", [id, pseudonym]);
116
+ }
117
+ if (subject.contactId) {
118
+ await db.query("DELETE FROM public.customer_identity_challenges WHERE client_id = $1", [subject.contactId]);
119
+ await db.query(`UPDATE public.clients SET email = $2, first_name = NULL, last_name = NULL, phone = NULL,
120
+ country = NULL, principal_id = NULL, updated_at = clock_timestamp() WHERE id = $1`, [subject.contactId, pseudonym]);
121
+ }
122
+ }
123
+ export function createPostgresPrivacyUnitOfWork(env, options = {}) {
124
+ const lane = createPostgresRoleFreeTransactionLane(env, options);
125
+ return { run: (work) => lane.run(db => work(db)), close: lane.close };
126
+ }
@@ -0,0 +1,10 @@
1
+ import type { HttpHandler } from "../http/index.js";
2
+ import type { AdminAuthorizationResult } from "./ports.js";
3
+ import { type IdentityResponseOptions } from "./response.js";
4
+ import { type PrivacyOperations } from "./privacyPorts.js";
5
+ export interface PrivacyHandlerDependencies extends IdentityResponseOptions {
6
+ authorizeAdmin: () => Promise<AdminAuthorizationResult>;
7
+ operations: PrivacyOperations;
8
+ }
9
+ export declare const createPrivacyExportHandler: (deps: PrivacyHandlerDependencies) => HttpHandler;
10
+ export declare const createPrivacyEraseHandler: (deps: PrivacyHandlerDependencies) => HttpHandler;
@@ -0,0 +1,35 @@
1
+ import { createIdentityResponder } from "./response.js";
2
+ import { privacyExportRequestSchema, privacyEraseRequestSchema, privacyExportResponseSchema, privacyEraseResponseSchema } from "./contracts/privacy.js";
3
+ import { PrivacyRefusal } from "./privacyPorts.js";
4
+ function handler(deps, erase) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(deps);
6
+ return async (req, res) => {
7
+ res.setHeader("Cache-Control", "no-store");
8
+ if (req.method !== "POST")
9
+ return sendMethodNotAllowed(res, ["POST"]);
10
+ try {
11
+ const auth = await deps.authorizeAdmin();
12
+ if (!auth.ok)
13
+ return sendBffError(res, auth.code, auth.message);
14
+ if (auth.role !== "admin" || auth.isMachineActor !== false)
15
+ return sendBffError(res, "FORBIDDEN", "Human administrator required");
16
+ const parsed = (erase ? privacyEraseRequestSchema : privacyExportRequestSchema).safeParse(req.body);
17
+ if (!parsed.success)
18
+ return sendBffError(res, "BAD_REQUEST", "Invalid privacy request");
19
+ const actor = { principalId: auth.principalId };
20
+ const result = erase ? privacyEraseResponseSchema.parse(await deps.operations.erase(actor, privacyEraseRequestSchema.parse(parsed.data)))
21
+ : privacyExportResponseSchema.parse(await deps.operations.export(actor, parsed.data.subject));
22
+ return sendBffSuccess(res, result);
23
+ }
24
+ catch (error) {
25
+ if (error instanceof PrivacyRefusal) {
26
+ const code = error.code === "PRIVACY_SUBJECT_NOT_FOUND" ? "NOT_FOUND"
27
+ : ["PRIVACY_ACTIVE_SUBSCRIPTION", "PRIVACY_RESUMABLE_SUBSCRIPTION", "PRIVACY_PROTECTED_OPERATOR", "PRIVACY_IDEMPOTENCY_CONFLICT", "PRIVACY_SUBJECT_ERASED"].includes(error.code) ? "CONFLICT" : "UPSTREAM_UNAVAILABLE";
28
+ return sendBffError(res, code, "Privacy operation refused", { details: { privacyCode: error.code } });
29
+ }
30
+ return sendBffError(res, "UPSTREAM_UNAVAILABLE", "Privacy operation unavailable", { details: { privacyCode: "PRIVACY_ASSESSMENT_UNAVAILABLE" } });
31
+ }
32
+ };
33
+ }
34
+ export const createPrivacyExportHandler = (deps) => handler(deps, false);
35
+ export const createPrivacyEraseHandler = (deps) => handler(deps, true);
@@ -0,0 +1,72 @@
1
+ import type { PrivacySubjectRef, PrivacyExport, ErasureReceipt, PersonalDataDocument, PersonalDataObligation, PrivacyJson, PrivacyRefusalCode } from "./contracts/privacy.js";
2
+ export declare class PrivacyRefusal extends Error {
3
+ readonly code: PrivacyRefusalCode;
4
+ constructor(code: PrivacyRefusalCode);
5
+ }
6
+ export interface AuthorizedHumanAdministrator {
7
+ principalId: string;
8
+ }
9
+ export interface ResolvedPrivacySubject {
10
+ contactId: string | null;
11
+ principalId: string | null;
12
+ /** Only explicitly selected identity fields; never credentials. */
13
+ identity: {
14
+ [key: string]: PrivacyJson;
15
+ };
16
+ }
17
+ export interface ErasureAssessment {
18
+ obligations: PersonalDataObligation[];
19
+ }
20
+ export interface ContributorDisposition {
21
+ contributorId: string;
22
+ version: string;
23
+ obligations: PersonalDataObligation[];
24
+ }
25
+ export interface PersonalDataContributor<Context> {
26
+ readonly id: string;
27
+ readonly version: string;
28
+ readonly ownedResources: readonly string[];
29
+ prepareErasure(context: Context): Promise<void>;
30
+ export(context: Context, subject: ResolvedPrivacySubject): Promise<PersonalDataDocument>;
31
+ assessErasure(context: Context, subject: ResolvedPrivacySubject): Promise<ErasureAssessment>;
32
+ eraseOrRetain(context: Context, subject: ResolvedPrivacySubject, assessment: ErasureAssessment, actor: AuthorizedHumanAdministrator): Promise<ContributorDisposition>;
33
+ }
34
+ export interface PersonalDataContributorRegistry<Context> {
35
+ readonly contributors: readonly PersonalDataContributor<Context>[];
36
+ readonly missingContributors: readonly string[];
37
+ }
38
+ export interface PrivacyUnitOfWork<Context> {
39
+ run<T>(work: (context: Context) => Promise<T>): Promise<T>;
40
+ }
41
+ export interface PrivacyStore<Context> {
42
+ readonly erasureSupported: boolean;
43
+ prepareErasure(context: Context): Promise<void>;
44
+ authorizeErasure(context: Context, actor: AuthorizedHumanAdministrator): Promise<void>;
45
+ resolve(context: Context, subject: PrivacySubjectRef, actor?: AuthorizedHumanAdministrator): Promise<ResolvedPrivacySubject | null>;
46
+ findReceipt(context: Context, subject: PrivacySubjectRef, key?: string): Promise<ErasureReceipt | null>;
47
+ eraseIdentity(context: Context, subject: ResolvedPrivacySubject, actor: AuthorizedHumanAdministrator, pseudonym: string): Promise<void>;
48
+ saveReceipt(context: Context, subject: ResolvedPrivacySubject, actor: AuthorizedHumanAdministrator, key: string, receipt: ErasureReceipt, dispositions: ContributorDisposition[]): Promise<void>;
49
+ }
50
+ export interface PrivacyUseCaseDependencies<Context> {
51
+ unitOfWork: PrivacyUnitOfWork<Context>;
52
+ registry: PersonalDataContributorRegistry<Context>;
53
+ store: PrivacyStore<Context>;
54
+ now(): Date;
55
+ randomId(): string;
56
+ }
57
+ export interface PrivacyOperations {
58
+ export(actor: AuthorizedHumanAdministrator, subject: PrivacySubjectRef): Promise<PrivacyExport>;
59
+ erase(actor: AuthorizedHumanAdministrator, input: {
60
+ subject: PrivacySubjectRef;
61
+ idempotencyKey: string;
62
+ }): Promise<ErasureReceipt>;
63
+ }
64
+ /** A host may compose this only after a committed local erasure; never delete an identity. */
65
+ export interface ProviderIdentityErasurePort {
66
+ disableIdentity(input: {
67
+ principalId: string;
68
+ committedErasureId: string;
69
+ }): Promise<{
70
+ status: "disabled" | "already_disabled";
71
+ }>;
72
+ }
@@ -0,0 +1,8 @@
1
+ export class PrivacyRefusal extends Error {
2
+ code;
3
+ constructor(code) {
4
+ super(code);
5
+ this.code = code;
6
+ this.name = "PrivacyRefusal";
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ import { type PrivacyUseCaseDependencies, type AuthorizedHumanAdministrator, type PrivacyOperations } from "./privacyPorts.js";
2
+ import type { PrivacySubjectRef, PrivacyEraseRequest, PrivacyExport, ErasureReceipt } from "./contracts/privacy.js";
3
+ export declare function exportPersonalData<C>(deps: PrivacyUseCaseDependencies<C>, actor: AuthorizedHumanAdministrator, input: PrivacySubjectRef): Promise<PrivacyExport>;
4
+ export declare function erasePersonalData<C>(deps: PrivacyUseCaseDependencies<C>, actor: AuthorizedHumanAdministrator, request: PrivacyEraseRequest): Promise<ErasureReceipt>;
5
+ export declare function createPrivacyOperations<C>(deps: PrivacyUseCaseDependencies<C>): PrivacyOperations;
@@ -0,0 +1,55 @@
1
+ import { privacyExportResponseSchema, erasureReceiptSchema, privacySubjectSchema, privacyEraseRequestSchema } from "./contracts/privacy.js";
2
+ import { PrivacyRefusal } from "./privacyPorts.js";
3
+ export async function exportPersonalData(deps, actor, input) {
4
+ const subject = privacySubjectSchema.parse(input);
5
+ return deps.unitOfWork.run(async (context) => {
6
+ const resolved = await deps.store.resolve(context, subject);
7
+ if (!resolved)
8
+ throw new PrivacyRefusal("PRIVACY_SUBJECT_NOT_FOUND");
9
+ const documents = [];
10
+ for (const contributor of deps.registry.contributors) {
11
+ const document = await contributor.export(context, resolved);
12
+ if (document.contributorId !== contributor.id || document.version !== contributor.version)
13
+ throw new PrivacyRefusal("PRIVACY_OWNERSHIP_UNDECLARED");
14
+ documents.push(document);
15
+ }
16
+ return privacyExportResponseSchema.parse({ format: "personal-data.v1", generatedAt: deps.now().toISOString(), subject, documents, missingContributors: [...deps.registry.missingContributors], erasure: await deps.store.findReceipt(context, subject) });
17
+ });
18
+ }
19
+ export async function erasePersonalData(deps, actor, request) {
20
+ const input = privacyEraseRequestSchema.parse(request);
21
+ // Capability refusal precedes even opening a managed transaction or provider request.
22
+ if (!deps.store.erasureSupported)
23
+ throw new PrivacyRefusal("MANAGED_PRIVACY_ERASURE_UNAVAILABLE");
24
+ if (deps.registry.missingContributors.length || !deps.registry.contributors.some(c => c.id === "identity"))
25
+ throw new PrivacyRefusal("PRIVACY_CONTRIBUTOR_MISSING");
26
+ return deps.unitOfWork.run(async (context) => {
27
+ await deps.store.prepareErasure(context);
28
+ // Domain relation locks precede subject/member row locks and remain held to commit.
29
+ for (const contributor of deps.registry.contributors)
30
+ await contributor.prepareErasure(context);
31
+ const resolved = await deps.store.resolve(context, input.subject, actor);
32
+ await deps.store.authorizeErasure(context, actor);
33
+ const previous = await deps.store.findReceipt(context, input.subject, input.idempotencyKey);
34
+ if (previous)
35
+ return erasureReceiptSchema.parse({ ...previous, outcome: "ALREADY_ERASED" });
36
+ if (!resolved)
37
+ throw new PrivacyRefusal("PRIVACY_SUBJECT_NOT_FOUND");
38
+ const assessments = [];
39
+ for (const contributor of deps.registry.contributors) {
40
+ const assessment = await contributor.assessErasure(context, resolved);
41
+ if (assessment.obligations.some(o => o.disposition === "unresolved"))
42
+ throw new PrivacyRefusal("PRIVACY_OWNERSHIP_UNDECLARED");
43
+ assessments.push(assessment);
44
+ }
45
+ const dispositions = [];
46
+ for (let i = 0; i < deps.registry.contributors.length; i++)
47
+ dispositions.push(await deps.registry.contributors[i].eraseOrRetain(context, resolved, assessments[i], actor));
48
+ const receipt = erasureReceiptSchema.parse({ version: "identity.erasure.v1", erasureId: deps.randomId(), completedAt: deps.now().toISOString(), outcome: "ERASED", obligations: dispositions.flatMap(d => d.obligations) });
49
+ await deps.store.saveReceipt(context, resolved, actor, input.idempotencyKey, receipt, dispositions);
50
+ return receipt;
51
+ });
52
+ }
53
+ export function createPrivacyOperations(deps) {
54
+ return { export: (actor, subject) => exportPersonalData(deps, actor, subject), erase: (actor, input) => erasePersonalData(deps, actor, input) };
55
+ }
@@ -0,0 +1,20 @@
1
+ import { type EmailTrustPolicy } from "./emailTrustPolicy.js";
2
+ import type { EmailTrustFailureCode } from "./contracts/emailTrust.js";
3
+ import type { AccountLinkStore } from "./ports.js";
4
+ export interface ReconcileAccountInput {
5
+ principalId: string;
6
+ email: string | null;
7
+ emailVerified: boolean;
8
+ }
9
+ export type ReconcileAccountErrorCode = EmailTrustFailureCode | "NO_VERIFIED_EMAIL" | "RESERVED_PRINCIPAL_REFUSED" | "PRINCIPAL_EMAIL_MISMATCH" | "ACCOUNT_LINK_CONFLICT" | "EMAIL_OWNED_BY_OTHER_PRINCIPAL";
10
+ export type ReconcileAccountResult = {
11
+ ok: true;
12
+ accountId: string;
13
+ created: boolean;
14
+ linked: boolean;
15
+ } | {
16
+ ok: false;
17
+ code: ReconcileAccountErrorCode;
18
+ existingPrincipalId?: string | null;
19
+ };
20
+ export declare function reconcileAccountForPrincipal(store: AccountLinkStore, input: ReconcileAccountInput, emailTrustPolicy: EmailTrustPolicy): Promise<ReconcileAccountResult>;
@@ -0,0 +1,66 @@
1
+ import { assessEmailTrust } from "./emailTrustPolicy.js";
2
+ export async function reconcileAccountForPrincipal(store, input, emailTrustPolicy) {
3
+ const email = normalizeEmail(input.email);
4
+ if (!email || !input.emailVerified) {
5
+ return { ok: false, code: "NO_VERIFIED_EMAIL" };
6
+ }
7
+ if (await store.isReservedPrincipal(input.principalId)) {
8
+ return { ok: false, code: "RESERVED_PRINCIPAL_REFUSED" };
9
+ }
10
+ const byPrincipal = await store.findAccountByPrincipalId(input.principalId);
11
+ if (byPrincipal) {
12
+ const accountEmail = normalizeEmail(byPrincipal.email);
13
+ if (accountEmail && accountEmail !== email) {
14
+ return { ok: false, code: "PRINCIPAL_EMAIL_MISMATCH", existingPrincipalId: input.principalId };
15
+ }
16
+ return { ok: true, accountId: byPrincipal.id, created: false, linked: false };
17
+ }
18
+ const trust = await assessEmailTrust(emailTrustPolicy);
19
+ if (!trust.trusted)
20
+ return { ok: false, code: trust.code };
21
+ const viaEmail = await resolveByEmail(store, email, input.principalId);
22
+ if (viaEmail)
23
+ return viaEmail;
24
+ try {
25
+ const created = await store.createAccount({
26
+ email,
27
+ principalId: input.principalId,
28
+ acquisitionSource: "social_signup",
29
+ lifecycleStage: "lead",
30
+ });
31
+ return { ok: true, accountId: created.id, created: true, linked: true };
32
+ }
33
+ catch (err) {
34
+ const retry = await resolveByEmail(store, email, input.principalId);
35
+ if (retry)
36
+ return retry;
37
+ throw err;
38
+ }
39
+ }
40
+ function normalizeEmail(email) {
41
+ return (email ?? "").trim().toLowerCase();
42
+ }
43
+ async function resolveByEmail(store, email, principalId) {
44
+ const account = await store.findAccountByEmail(email);
45
+ if (!account)
46
+ return null;
47
+ if (account.principalId === principalId) {
48
+ return { ok: true, accountId: account.id, created: false, linked: false };
49
+ }
50
+ if (account.principalId !== null) {
51
+ return {
52
+ ok: false,
53
+ code: "EMAIL_OWNED_BY_OTHER_PRINCIPAL",
54
+ existingPrincipalId: account.principalId,
55
+ };
56
+ }
57
+ const link = await store.linkPrincipalToAccount(account.id, principalId);
58
+ if (link.ok === true) {
59
+ return { ok: true, accountId: account.id, created: false, linked: true };
60
+ }
61
+ return {
62
+ ok: false,
63
+ code: "ACCOUNT_LINK_CONFLICT",
64
+ existingPrincipalId: link.existingPrincipalId,
65
+ };
66
+ }
@@ -0,0 +1,11 @@
1
+ import type { EmailTrustPolicy } from "./emailTrustPolicy.js";
2
+ import { type IdentityResponseOptions } from "./response.js";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import type { AccountLinkStore, IdentityVerifierPort } from "./ports.js";
5
+ import { type ReconcileAccountErrorCode } from "./reconcileAccountForPrincipal.js";
6
+ export declare function createReconcileAccountHandler(options: IdentityResponseOptions & {
7
+ verifier: IdentityVerifierPort;
8
+ store: AccountLinkStore;
9
+ emailTrustPolicy: EmailTrustPolicy;
10
+ message?: (code: ReconcileAccountErrorCode) => string;
11
+ }): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { createIdentityResponder } from "./response.js";
2
+ import { reconcileAccountForPrincipal } from "./reconcileAccountForPrincipal.js";
3
+ import { readBearerToken } from "./adminAuthorization.js";
4
+ export function createReconcileAccountHandler(options) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
6
+ return async (req, res) => {
7
+ if (req.method !== "POST")
8
+ return sendMethodNotAllowed(res, ["POST"]);
9
+ const token = readBearerToken(req);
10
+ if (!token)
11
+ return sendBffError(res, "UNAUTHORIZED", "Customer session required");
12
+ try {
13
+ const principal = await options.verifier.verifyAccessToken(token);
14
+ if (!principal)
15
+ return sendBffError(res, "UNAUTHORIZED", "Customer session required");
16
+ const result = await reconcileAccountForPrincipal(options.store, principal, options.emailTrustPolicy);
17
+ if (!result.ok) {
18
+ const code = result.code === "NO_VERIFIED_EMAIL" ? "BAD_REQUEST"
19
+ : result.code === "EMAIL_TRUST_SETTINGS_UNAVAILABLE" ? "UPSTREAM_UNAVAILABLE"
20
+ : result.code === "RESERVED_PRINCIPAL_REFUSED" || result.code === "EMAIL_TRUST_ENVELOPE_REFUSED" ? "FORBIDDEN" : "CONFLICT";
21
+ sendBffError(res, code, options.message?.(result.code) ?? result.code, { details: { code: result.code } });
22
+ return;
23
+ }
24
+ sendBffSuccess(res, { accountId: result.accountId, created: result.created, linked: result.linked });
25
+ }
26
+ catch {
27
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Account reconciliation failed");
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,31 @@
1
+ import type { OperatorInvitationStore } from "./operatorInvitationPorts.js";
2
+ import { type IdentityMessages } from "./messages.js";
3
+ export type RemoveAdminUserResponse = {
4
+ revoked?: true;
5
+ error?: string;
6
+ code?: string;
7
+ };
8
+ export interface RemoveAdminUserGateway {
9
+ getUser: (accessToken: string) => Promise<{
10
+ user: {
11
+ id: string;
12
+ } | null;
13
+ error: unknown;
14
+ }>;
15
+ findAdminUser: (userId: string) => Promise<{
16
+ id: string;
17
+ role: string;
18
+ } | null>;
19
+ revokeAdminUser: (userId: string) => Promise<{
20
+ error: {
21
+ message: string;
22
+ } | null;
23
+ }>;
24
+ }
25
+ export declare function runRemoveAdminUserUseCase(request: {
26
+ accessToken: string | null;
27
+ userId: string;
28
+ }, gateway: RemoveAdminUserGateway, messages?: IdentityMessages, invitationStore?: Pick<OperatorInvitationStore, "remove">): Promise<{
29
+ status: number;
30
+ body: RemoveAdminUserResponse;
31
+ }>;