@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.
- package/CHANGELOG.md +124 -0
- package/README.md +633 -6
- package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
- package/dist/communications/accountingDocumentDelivery.js +33 -0
- package/dist/communications/authEmailError.d.ts +5 -0
- package/dist/communications/authEmailError.js +8 -0
- package/dist/communications/authEmailPorts.d.ts +2 -0
- package/dist/communications/authEmailPorts.js +1 -0
- package/dist/communications/authSendEmailHook.d.ts +35 -0
- package/dist/communications/authSendEmailHook.js +250 -0
- package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
- package/dist/communications/capturedTransactionalDelivery.js +14 -0
- package/dist/communications/communicationSync.d.ts +11 -0
- package/dist/communications/communicationSync.js +79 -0
- package/dist/communications/communicationSyncWorker.d.ts +68 -0
- package/dist/communications/communicationSyncWorker.js +218 -0
- package/dist/communications/consentPolicy.d.ts +19 -0
- package/dist/communications/consentPolicy.js +43 -0
- package/dist/communications/consentPurposeRegistry.d.ts +4 -0
- package/dist/communications/consentPurposeRegistry.js +24 -0
- package/dist/communications/contracts/abandonedCart.d.ts +27 -0
- package/dist/communications/contracts/abandonedCart.js +25 -0
- package/dist/communications/contracts/authEmail.d.ts +62 -0
- package/dist/communications/contracts/authEmail.js +1 -0
- package/dist/communications/contracts/backInStock.d.ts +28 -0
- package/dist/communications/contracts/backInStock.js +28 -0
- package/dist/communications/contracts/blocks.d.ts +24 -0
- package/dist/communications/contracts/blocks.js +23 -0
- package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
- package/dist/communications/contracts/checkoutExpired.js +25 -0
- package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
- package/dist/communications/contracts/checkoutRecovery.js +37 -0
- package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
- package/dist/communications/contracts/commerceEmailContent.js +1 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
- package/dist/communications/contracts/consent.d.ts +12 -0
- package/dist/communications/contracts/consent.js +1 -0
- package/dist/communications/contracts/email.d.ts +182 -0
- package/dist/communications/contracts/email.js +1 -0
- package/dist/communications/contracts/guideCard.d.ts +3 -0
- package/dist/communications/contracts/guideCard.js +26 -0
- package/dist/communications/contracts/index.d.ts +51 -0
- package/dist/communications/contracts/index.js +42 -0
- package/dist/communications/contracts/inquiryContent.d.ts +32 -0
- package/dist/communications/contracts/inquiryContent.js +33 -0
- package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
- package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
- package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
- package/dist/communications/contracts/marketingAutomation.js +1 -0
- package/dist/communications/contracts/newsletter.d.ts +63 -0
- package/dist/communications/contracts/newsletter.js +1 -0
- package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
- package/dist/communications/contracts/newsletterWebhook.js +1 -0
- package/dist/communications/contracts/optOutContent.d.ts +11 -0
- package/dist/communications/contracts/optOutContent.js +17 -0
- package/dist/communications/contracts/orderCanceled.d.ts +25 -0
- package/dist/communications/contracts/orderCanceled.js +27 -0
- package/dist/communications/contracts/orderDraft.d.ts +46 -0
- package/dist/communications/contracts/orderDraft.js +46 -0
- package/dist/communications/contracts/orderPaid.d.ts +53 -0
- package/dist/communications/contracts/orderPaid.js +71 -0
- package/dist/communications/contracts/orderRefunded.d.ts +24 -0
- package/dist/communications/contracts/orderRefunded.js +27 -0
- package/dist/communications/contracts/paymentFailed.d.ts +24 -0
- package/dist/communications/contracts/paymentFailed.js +24 -0
- package/dist/communications/contracts/publicIntake.d.ts +49 -0
- package/dist/communications/contracts/publicIntake.js +1 -0
- package/dist/communications/contracts/render.d.ts +2 -0
- package/dist/communications/contracts/render.js +229 -0
- package/dist/communications/contracts/reorderReminder.d.ts +24 -0
- package/dist/communications/contracts/reorderReminder.js +24 -0
- package/dist/communications/contracts/returnEmails.d.ts +25 -0
- package/dist/communications/contracts/returnEmails.js +62 -0
- package/dist/communications/contracts/reviewEffects.d.ts +26 -0
- package/dist/communications/contracts/reviewEffects.js +25 -0
- package/dist/communications/contracts/reviewRequest.d.ts +26 -0
- package/dist/communications/contracts/reviewRequest.js +24 -0
- package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
- package/dist/communications/contracts/roleNotificationContent.js +46 -0
- package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
- package/dist/communications/contracts/shipmentDelivered.js +24 -0
- package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
- package/dist/communications/contracts/shipmentDispatched.js +35 -0
- package/dist/communications/contracts/shipmentException.d.ts +21 -0
- package/dist/communications/contracts/shipmentException.js +26 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
- package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
- package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
- package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
- package/dist/communications/contracts/subscriptionCancelled.js +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
- package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
- package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
- package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
- package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
- package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
- package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
- package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
- package/dist/communications/contracts/subscriptionPaused.js +25 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
- package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
- package/dist/communications/contracts/subscriptionResumed.js +25 -0
- package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
- package/dist/communications/contracts/subscriptionWelcome.js +45 -0
- package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
- package/dist/communications/contracts/subscriptionWinback.js +28 -0
- package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
- package/dist/communications/contracts/transactionalDelivery.js +1 -0
- package/dist/communications/contracts/types.d.ts +46 -0
- package/dist/communications/contracts/types.js +1 -0
- package/dist/communications/contribution.d.ts +10 -0
- package/dist/communications/contribution.js +24 -0
- package/dist/communications/deliveryReconciliation.d.ts +64 -0
- package/dist/communications/deliveryReconciliation.js +60 -0
- package/dist/communications/emailEgress.d.ts +38 -0
- package/dist/communications/emailEgress.js +100 -0
- package/dist/communications/emailHealthAssessment.d.ts +39 -0
- package/dist/communications/emailHealthAssessment.js +59 -0
- package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
- package/dist/communications/emailHealthMetricsPort.js +1 -0
- package/dist/communications/emailNotificationControl.d.ts +27 -0
- package/dist/communications/emailNotificationControl.js +10 -0
- package/dist/communications/emailObservation.d.ts +28 -0
- package/dist/communications/emailObservation.js +23 -0
- package/dist/communications/emailTransport.d.ts +82 -0
- package/dist/communications/emailTransport.js +95 -0
- package/dist/communications/errors.d.ts +15 -0
- package/dist/communications/errors.js +30 -0
- package/dist/communications/index.d.ts +46 -0
- package/dist/communications/index.js +38 -0
- package/dist/communications/integrationEventSignature.d.ts +15 -0
- package/dist/communications/integrationEventSignature.js +37 -0
- package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
- package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
- package/dist/communications/nativeOperations.d.ts +79 -0
- package/dist/communications/nativeOperations.js +3 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
- package/dist/communications/newsletterIntegrationContracts.js +128 -0
- package/dist/communications/newsletterWebhookPort.d.ts +11 -0
- package/dist/communications/newsletterWebhookPort.js +84 -0
- package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
- package/dist/communications/newsletterWebhookProcessing.js +71 -0
- package/dist/communications/originPolicy.d.ts +40 -0
- package/dist/communications/originPolicy.js +100 -0
- package/dist/communications/postgresCommunicationSync.d.ts +79 -0
- package/dist/communications/postgresCommunicationSync.js +111 -0
- package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
- package/dist/communications/postgresConsentEvaluator.js +43 -0
- package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
- package/dist/communications/postgresDeliveryReconciliation.js +113 -0
- package/dist/communications/postgresNativeOperations.d.ts +13 -0
- package/dist/communications/postgresNativeOperations.js +63 -0
- package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
- package/dist/communications/postgresNotificationRecipients.js +60 -0
- package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
- package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
- package/dist/communications/preferencesToken.d.ts +10 -0
- package/dist/communications/preferencesToken.js +40 -0
- package/dist/communications/publicPreferenceToken.d.ts +24 -0
- package/dist/communications/publicPreferenceToken.js +53 -0
- package/dist/communications/shipmentNotifications.d.ts +34 -0
- package/dist/communications/shipmentNotifications.js +61 -0
- package/dist/communications/sql.d.ts +26 -0
- package/dist/communications/sql.js +7 -0
- package/dist/communications/standardWebhooks.d.ts +14 -0
- package/dist/communications/standardWebhooks.js +73 -0
- package/dist/communications/status.d.ts +11 -0
- package/dist/communications/status.js +36 -0
- package/dist/communications/transactionalDelivery.d.ts +19 -0
- package/dist/communications/transactionalDelivery.js +96 -0
- package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
- package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
- package/dist/communications/unsubscribeToken.d.ts +77 -0
- package/dist/communications/unsubscribeToken.js +150 -0
- package/dist/communications/validation.d.ts +2 -0
- package/dist/communications/validation.js +3 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/webhookSignature.d.ts +42 -0
- package/dist/http/webhookSignature.js +86 -0
- package/dist/identity/adminAuthBinding.d.ts +28 -0
- package/dist/identity/adminAuthBinding.js +66 -0
- package/dist/identity/adminAuthorization.d.ts +34 -0
- package/dist/identity/adminAuthorization.js +72 -0
- package/dist/identity/adminContracts.d.ts +70 -0
- package/dist/identity/adminContracts.js +43 -0
- package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
- package/dist/identity/adminMagicLinkHandler.js +94 -0
- package/dist/identity/adminMeHandler.d.ts +15 -0
- package/dist/identity/adminMeHandler.js +49 -0
- package/dist/identity/adminPreflight.d.ts +16 -0
- package/dist/identity/adminPreflight.js +38 -0
- package/dist/identity/adminUserActionPorts.d.ts +31 -0
- package/dist/identity/adminUserActionPorts.js +51 -0
- package/dist/identity/adminUserHandlers.d.ts +38 -0
- package/dist/identity/adminUserHandlers.js +140 -0
- package/dist/identity/authVerifyUrl.d.ts +6 -0
- package/dist/identity/authVerifyUrl.js +54 -0
- package/dist/identity/contracts/contracts.d.ts +8 -0
- package/dist/identity/contracts/contracts.js +2 -0
- package/dist/identity/contracts/emailTrust.d.ts +3 -0
- package/dist/identity/contracts/emailTrust.js +4 -0
- package/dist/identity/contracts/index.d.ts +8 -0
- package/dist/identity/contracts/index.js +8 -0
- package/dist/identity/contracts/oidc.d.ts +3 -0
- package/dist/identity/contracts/oidc.js +9 -0
- package/dist/identity/contracts/operatorAuth.d.ts +35 -0
- package/dist/identity/contracts/operatorAuth.js +53 -0
- package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
- package/dist/identity/contracts/operatorInvitations.js +1 -0
- package/dist/identity/contracts/ports.d.ts +22 -0
- package/dist/identity/contracts/ports.js +1 -0
- package/dist/identity/contracts/privacy.d.ts +62 -0
- package/dist/identity/contracts/privacy.js +99 -0
- package/dist/identity/contracts/providerList.d.ts +3 -0
- package/dist/identity/contracts/providerList.js +11 -0
- package/dist/identity/contracts/types.d.ts +16 -0
- package/dist/identity/contracts/types.js +4 -0
- package/dist/identity/contribution.d.ts +8 -0
- package/dist/identity/contribution.js +22 -0
- package/dist/identity/customerAuth.d.ts +8 -0
- package/dist/identity/customerAuth.js +1 -0
- package/dist/identity/customerAuthorization.d.ts +5 -0
- package/dist/identity/customerAuthorization.js +22 -0
- package/dist/identity/customerContracts.d.ts +42 -0
- package/dist/identity/customerContracts.js +43 -0
- package/dist/identity/customerIdentityBinding.d.ts +44 -0
- package/dist/identity/customerIdentityBinding.js +60 -0
- package/dist/identity/customerIdentityVerifier.d.ts +8 -0
- package/dist/identity/customerIdentityVerifier.js +101 -0
- package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
- package/dist/identity/customerMagicLinkHandler.js +115 -0
- package/dist/identity/customerMeHandler.d.ts +11 -0
- package/dist/identity/customerMeHandler.js +46 -0
- package/dist/identity/customerPorts.d.ts +4 -0
- package/dist/identity/customerPorts.js +1 -0
- package/dist/identity/customerSessionBinding.d.ts +38 -0
- package/dist/identity/customerSessionBinding.js +80 -0
- package/dist/identity/customerSessionIssuer.d.ts +10 -0
- package/dist/identity/customerSessionIssuer.js +57 -0
- package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
- package/dist/identity/customerVerifyOtpHandler.js +41 -0
- package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
- package/dist/identity/directOperatorInvitationStore.js +193 -0
- package/dist/identity/emailTrustPolicy.d.ts +14 -0
- package/dist/identity/emailTrustPolicy.js +16 -0
- package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
- package/dist/identity/identityPersonalDataContributor.js +46 -0
- package/dist/identity/identityReadiness.d.ts +10 -0
- package/dist/identity/identityReadiness.js +25 -0
- package/dist/identity/index.d.ts +67 -0
- package/dist/identity/index.js +66 -0
- package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
- package/dist/identity/inviteAdminUserUseCase.js +75 -0
- package/dist/identity/jwtLibrary.d.ts +54 -0
- package/dist/identity/jwtLibrary.js +1 -0
- package/dist/identity/machineActorWriteFence.d.ts +4 -0
- package/dist/identity/machineActorWriteFence.js +13 -0
- package/dist/identity/magicLinkPorts.d.ts +28 -0
- package/dist/identity/magicLinkPorts.js +1 -0
- package/dist/identity/managedAccountLinkStore.d.ts +2 -0
- package/dist/identity/managedAccountLinkStore.js +93 -0
- package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
- package/dist/identity/managedAdminAuthVerifier.js +27 -0
- package/dist/identity/managedAdminUserGateways.d.ts +123 -0
- package/dist/identity/managedAdminUserGateways.js +127 -0
- package/dist/identity/managedClient.d.ts +90 -0
- package/dist/identity/managedClient.js +10 -0
- package/dist/identity/managedClientAuthLink.d.ts +3 -0
- package/dist/identity/managedClientAuthLink.js +115 -0
- package/dist/identity/managedCustomerMe.d.ts +13 -0
- package/dist/identity/managedCustomerMe.js +30 -0
- package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
- package/dist/identity/managedEmailTrustPolicy.js +105 -0
- package/dist/identity/managedIdentityVerifier.d.ts +2 -0
- package/dist/identity/managedIdentityVerifier.js +16 -0
- package/dist/identity/managedPrivacy.d.ts +4 -0
- package/dist/identity/managedPrivacy.js +39 -0
- package/dist/identity/messages.d.ts +29 -0
- package/dist/identity/messages.js +39 -0
- package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
- package/dist/identity/oidcIdentityVerifier.js +309 -0
- package/dist/identity/oidcPrincipal.d.ts +2 -0
- package/dist/identity/oidcPrincipal.js +10 -0
- package/dist/identity/operatorAdminAuth.d.ts +6 -0
- package/dist/identity/operatorAdminAuth.js +13 -0
- package/dist/identity/operatorAuthHandlers.d.ts +18 -0
- package/dist/identity/operatorAuthHandlers.js +71 -0
- package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
- package/dist/identity/operatorIdentityVerifier.js +19 -0
- package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
- package/dist/identity/operatorInvitationLifecycle.js +15 -0
- package/dist/identity/operatorInvitationPorts.d.ts +31 -0
- package/dist/identity/operatorInvitationPorts.js +1 -0
- package/dist/identity/operatorPorts.d.ts +37 -0
- package/dist/identity/operatorPorts.js +1 -0
- package/dist/identity/operatorSession.d.ts +13 -0
- package/dist/identity/operatorSession.js +31 -0
- package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
- package/dist/identity/operatorTokenAdminAuth.js +50 -0
- package/dist/identity/personalDataRegistry.d.ts +4 -0
- package/dist/identity/personalDataRegistry.js +11 -0
- package/dist/identity/platformAdminAuth.d.ts +15 -0
- package/dist/identity/platformAdminAuth.js +1 -0
- package/dist/identity/ports.d.ts +127 -0
- package/dist/identity/ports.js +38 -0
- package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
- package/dist/identity/postgresAccountLinkStore.js +52 -0
- package/dist/identity/postgresCustomerMe.d.ts +3 -0
- package/dist/identity/postgresCustomerMe.js +21 -0
- package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
- package/dist/identity/postgresCustomerRecovery.js +57 -0
- package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
- package/dist/identity/postgresOperatorIdentity.js +137 -0
- package/dist/identity/postgresPrivacy.d.ts +7 -0
- package/dist/identity/postgresPrivacy.js +126 -0
- package/dist/identity/privacyHandlers.d.ts +10 -0
- package/dist/identity/privacyHandlers.js +35 -0
- package/dist/identity/privacyPorts.d.ts +72 -0
- package/dist/identity/privacyPorts.js +8 -0
- package/dist/identity/privacyUseCases.d.ts +5 -0
- package/dist/identity/privacyUseCases.js +55 -0
- package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
- package/dist/identity/reconcileAccountForPrincipal.js +66 -0
- package/dist/identity/reconcileAccountHandler.d.ts +11 -0
- package/dist/identity/reconcileAccountHandler.js +30 -0
- package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
- package/dist/identity/removeAdminUserUseCase.js +60 -0
- package/dist/identity/response.d.ts +3 -0
- package/dist/identity/response.js +2 -0
- package/dist/identity/sql.d.ts +44 -0
- package/dist/identity/sql.js +10 -0
- package/package.json +22 -2
- package/release-gates.json +63 -8
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260810180001_self_host_actor_auth.sql +40 -0
- package/sql/20260810204057_customer_identity_read.sql +31 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
- package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
- package/sql/20261018120000_identity_operator_invitations.sql +42 -0
- package/sql/20261018180000_identity_operator_challenges.sql +56 -0
- package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
- package/sql/20261019200000_communications_native_operations.sql +68 -0
- package/src/communications/accountingDocumentDelivery.ts +63 -0
- package/src/communications/authEmailError.ts +10 -0
- package/src/communications/authEmailPorts.ts +2 -0
- package/src/communications/authSendEmailHook.ts +310 -0
- package/src/communications/capturedTransactionalDelivery.ts +29 -0
- package/src/communications/communicationSync.ts +96 -0
- package/src/communications/communicationSyncWorker.ts +302 -0
- package/src/communications/consentPolicy.ts +82 -0
- package/src/communications/consentPurposeRegistry.ts +31 -0
- package/src/communications/contracts/abandonedCart.ts +64 -0
- package/src/communications/contracts/authEmail.ts +73 -0
- package/src/communications/contracts/backInStock.ts +69 -0
- package/src/communications/contracts/blocks.ts +47 -0
- package/src/communications/contracts/checkoutExpired.ts +62 -0
- package/src/communications/contracts/checkoutRecovery.ts +68 -0
- package/src/communications/contracts/commerceEmailContent.ts +189 -0
- package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
- package/src/communications/contracts/consent.ts +13 -0
- package/src/communications/contracts/email.ts +210 -0
- package/src/communications/contracts/guideCard.ts +34 -0
- package/src/communications/contracts/index.ts +51 -0
- package/src/communications/contracts/inquiryContent.ts +53 -0
- package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
- package/src/communications/contracts/marketingAutomation.ts +45 -0
- package/src/communications/contracts/newsletter.ts +78 -0
- package/src/communications/contracts/newsletterWebhook.ts +23 -0
- package/src/communications/contracts/optOutContent.ts +23 -0
- package/src/communications/contracts/orderCanceled.ts +64 -0
- package/src/communications/contracts/orderDraft.ts +112 -0
- package/src/communications/contracts/orderPaid.ts +147 -0
- package/src/communications/contracts/orderRefunded.ts +63 -0
- package/src/communications/contracts/paymentFailed.ts +59 -0
- package/src/communications/contracts/publicIntake.ts +53 -0
- package/src/communications/contracts/render.ts +271 -0
- package/src/communications/contracts/reorderReminder.ts +59 -0
- package/src/communications/contracts/returnEmails.ts +113 -0
- package/src/communications/contracts/reviewEffects.ts +60 -0
- package/src/communications/contracts/reviewRequest.ts +59 -0
- package/src/communications/contracts/roleNotificationContent.ts +65 -0
- package/src/communications/contracts/shipmentDelivered.ts +59 -0
- package/src/communications/contracts/shipmentDispatched.ts +77 -0
- package/src/communications/contracts/shipmentException.ts +62 -0
- package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
- package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
- package/src/communications/contracts/subscriptionCancelled.ts +57 -0
- package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
- package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
- package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
- package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
- package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
- package/src/communications/contracts/subscriptionPaused.ts +58 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
- package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
- package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
- package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
- package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
- package/src/communications/contracts/subscriptionResumed.ts +60 -0
- package/src/communications/contracts/subscriptionWelcome.ts +102 -0
- package/src/communications/contracts/subscriptionWinback.ts +62 -0
- package/src/communications/contracts/transactionalDelivery.ts +43 -0
- package/src/communications/contracts/types.ts +58 -0
- package/src/communications/contribution.ts +33 -0
- package/src/communications/deliveryReconciliation.ts +108 -0
- package/src/communications/emailEgress.ts +111 -0
- package/src/communications/emailHealthAssessment.ts +106 -0
- package/src/communications/emailHealthMetricsPort.ts +17 -0
- package/src/communications/emailNotificationControl.ts +28 -0
- package/src/communications/emailObservation.ts +43 -0
- package/src/communications/emailTransport.ts +167 -0
- package/src/communications/errors.ts +34 -0
- package/src/communications/index.ts +46 -0
- package/src/communications/integrationEventSignature.ts +49 -0
- package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
- package/src/communications/nativeOperations.ts +40 -0
- package/src/communications/newsletterIntegrationContracts.ts +154 -0
- package/src/communications/newsletterWebhookPort.ts +104 -0
- package/src/communications/newsletterWebhookProcessing.ts +102 -0
- package/src/communications/originPolicy.ts +151 -0
- package/src/communications/postgresCommunicationSync.ts +127 -0
- package/src/communications/postgresConsentEvaluator.ts +49 -0
- package/src/communications/postgresDeliveryReconciliation.ts +101 -0
- package/src/communications/postgresNativeOperations.ts +55 -0
- package/src/communications/postgresNotificationRecipients.ts +57 -0
- package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
- package/src/communications/preferencesToken.ts +62 -0
- package/src/communications/publicPreferenceToken.ts +68 -0
- package/src/communications/shipmentNotifications.ts +78 -0
- package/src/communications/sql.ts +7 -0
- package/src/communications/standardWebhooks.ts +84 -0
- package/src/communications/status.ts +51 -0
- package/src/communications/transactionalDelivery.ts +114 -0
- package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
- package/src/communications/unsubscribeToken.ts +186 -0
- package/src/communications/validation.ts +3 -0
- package/src/http/index.ts +1 -0
- package/src/http/webhookSignature.ts +96 -0
- package/src/identity/adminAuthBinding.ts +76 -0
- package/src/identity/adminAuthorization.ts +106 -0
- package/src/identity/adminContracts.ts +52 -0
- package/src/identity/adminMagicLinkHandler.ts +132 -0
- package/src/identity/adminMeHandler.ts +64 -0
- package/src/identity/adminPreflight.ts +46 -0
- package/src/identity/adminUserActionPorts.ts +78 -0
- package/src/identity/adminUserHandlers.ts +174 -0
- package/src/identity/authVerifyUrl.ts +56 -0
- package/src/identity/contracts/contracts.ts +15 -0
- package/src/identity/contracts/emailTrust.ts +5 -0
- package/src/identity/contracts/index.ts +8 -0
- package/src/identity/contracts/oidc.ts +10 -0
- package/src/identity/contracts/operatorAuth.ts +61 -0
- package/src/identity/contracts/operatorInvitations.ts +32 -0
- package/src/identity/contracts/ports.ts +25 -0
- package/src/identity/contracts/privacy.ts +86 -0
- package/src/identity/contracts/providerList.ts +13 -0
- package/src/identity/contracts/types.ts +25 -0
- package/src/identity/contribution.ts +29 -0
- package/src/identity/customerAuth.ts +3 -0
- package/src/identity/customerAuthorization.ts +38 -0
- package/src/identity/customerContracts.ts +52 -0
- package/src/identity/customerIdentityBinding.ts +125 -0
- package/src/identity/customerIdentityVerifier.ts +141 -0
- package/src/identity/customerMagicLinkHandler.ts +171 -0
- package/src/identity/customerMeHandler.ts +64 -0
- package/src/identity/customerPorts.ts +2 -0
- package/src/identity/customerSessionBinding.ts +114 -0
- package/src/identity/customerSessionIssuer.ts +71 -0
- package/src/identity/customerVerifyOtpHandler.ts +44 -0
- package/src/identity/directOperatorInvitationStore.ts +178 -0
- package/src/identity/emailTrustPolicy.ts +16 -0
- package/src/identity/identityPersonalDataContributor.ts +48 -0
- package/src/identity/identityReadiness.ts +26 -0
- package/src/identity/index.ts +69 -0
- package/src/identity/inviteAdminUserUseCase.ts +134 -0
- package/src/identity/jwtLibrary.ts +24 -0
- package/src/identity/machineActorWriteFence.ts +19 -0
- package/src/identity/magicLinkPorts.ts +7 -0
- package/src/identity/managedAccountLinkStore.ts +105 -0
- package/src/identity/managedAdminAuthVerifier.ts +40 -0
- package/src/identity/managedAdminUserGateways.ts +164 -0
- package/src/identity/managedClient.ts +58 -0
- package/src/identity/managedClientAuthLink.ts +120 -0
- package/src/identity/managedCustomerMe.ts +52 -0
- package/src/identity/managedEmailTrustPolicy.ts +90 -0
- package/src/identity/managedIdentityVerifier.ts +20 -0
- package/src/identity/managedPrivacy.ts +37 -0
- package/src/identity/messages.ts +41 -0
- package/src/identity/oidcIdentityVerifier.ts +229 -0
- package/src/identity/oidcPrincipal.ts +10 -0
- package/src/identity/operatorAdminAuth.ts +14 -0
- package/src/identity/operatorAuthHandlers.ts +52 -0
- package/src/identity/operatorIdentityVerifier.ts +19 -0
- package/src/identity/operatorInvitationLifecycle.ts +14 -0
- package/src/identity/operatorInvitationPorts.ts +11 -0
- package/src/identity/operatorPorts.ts +11 -0
- package/src/identity/operatorSession.ts +30 -0
- package/src/identity/operatorTokenAdminAuth.ts +79 -0
- package/src/identity/personalDataRegistry.ts +8 -0
- package/src/identity/platformAdminAuth.ts +7 -0
- package/src/identity/ports.ts +158 -0
- package/src/identity/postgresAccountLinkStore.ts +49 -0
- package/src/identity/postgresCustomerMe.ts +33 -0
- package/src/identity/postgresCustomerRecovery.ts +72 -0
- package/src/identity/postgresOperatorIdentity.ts +123 -0
- package/src/identity/postgresPrivacy.ts +117 -0
- package/src/identity/privacyHandlers.ts +33 -0
- package/src/identity/privacyPorts.ts +52 -0
- package/src/identity/privacyUseCases.ts +48 -0
- package/src/identity/reconcileAccountForPrincipal.ts +99 -0
- package/src/identity/reconcileAccountHandler.ts +36 -0
- package/src/identity/removeAdminUserUseCase.ts +85 -0
- package/src/identity/response.ts +3 -0
- package/src/identity/sql.ts +10 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface TransactionalDeliveryCommand {
|
|
2
|
+
readonly idempotencyKey: string;
|
|
3
|
+
/** Opaque recipient reference; never persisted outside the command fingerprint. */
|
|
4
|
+
readonly recipientReference: string;
|
|
5
|
+
/** Opaque template reference; never persisted outside the command fingerprint. */
|
|
6
|
+
readonly templateReference: string;
|
|
7
|
+
/** Optional bounded reference to an expiring/revocable access grant. */
|
|
8
|
+
readonly accessGrantReference?: string;
|
|
9
|
+
}
|
|
10
|
+
export type TransactionalDeliveryReceipt = {
|
|
11
|
+
readonly idempotencyKey: string;
|
|
12
|
+
readonly commandFingerprint: string;
|
|
13
|
+
readonly state: "accepted";
|
|
14
|
+
readonly deliveryReference: string;
|
|
15
|
+
readonly errorCode: null;
|
|
16
|
+
readonly attemptCount: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly idempotencyKey: string;
|
|
19
|
+
readonly commandFingerprint: string;
|
|
20
|
+
readonly state: "failed";
|
|
21
|
+
readonly deliveryReference: null;
|
|
22
|
+
readonly errorCode: string;
|
|
23
|
+
readonly attemptCount: number;
|
|
24
|
+
};
|
|
25
|
+
export interface TransactionalDeliveryReceiptStore {
|
|
26
|
+
readReceipt(idempotencyKey: string): Promise<TransactionalDeliveryReceipt | null>;
|
|
27
|
+
recordAccepted(input: {
|
|
28
|
+
idempotencyKey: string;
|
|
29
|
+
commandFingerprint: string;
|
|
30
|
+
deliveryReference: string;
|
|
31
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
32
|
+
recordFailed(input: {
|
|
33
|
+
idempotencyKey: string;
|
|
34
|
+
commandFingerprint: string;
|
|
35
|
+
errorCode: string;
|
|
36
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
37
|
+
}
|
|
38
|
+
export interface CapturedTransactionalDeliveryPort {
|
|
39
|
+
deliver(command: TransactionalDeliveryCommand): Promise<{
|
|
40
|
+
deliveryReference: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
type COMMUNICATION_CHANNEL = "email";
|
|
2
|
+
type EMAIL_SEND_STATUSES = "queued" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "skipped";
|
|
3
|
+
type EMAIL_SEND_SKIP_REASONS = "sequence_paused" | "already_sent" | "template_unavailable" | "race_lost" | "policy_blocked" | "admin_disabled";
|
|
4
|
+
export type CommunicationChannel = COMMUNICATION_CHANNEL;
|
|
5
|
+
/** The host supplies its persisted provider vocabulary. */
|
|
6
|
+
export type CommunicationProvider<Provider extends string = string> = Provider;
|
|
7
|
+
export type EmailSendStatus = EMAIL_SEND_STATUSES;
|
|
8
|
+
export type EmailSendSkipReason = EMAIL_SEND_SKIP_REASONS;
|
|
9
|
+
type COMMUNICATION_PURPOSES = "transactional" | "tester_program" | "marketing_launch_offer" | "marketing_newsletter" | "subscription_dunning" | "admin_notification";
|
|
10
|
+
type COMMUNICATION_PERMISSION_STATES = "unknown" | "granted" | "denied" | "suppressed";
|
|
11
|
+
type COMMUNICATION_POLICY_DECISIONS = "allowed" | "blocked";
|
|
12
|
+
export type CommunicationPurpose = COMMUNICATION_PURPOSES;
|
|
13
|
+
export type CommunicationPermissionState = COMMUNICATION_PERMISSION_STATES;
|
|
14
|
+
export type CommunicationPolicyDecision = COMMUNICATION_POLICY_DECISIONS;
|
|
15
|
+
export interface CommunicationRecipientRef {
|
|
16
|
+
system: "supabase";
|
|
17
|
+
table: "testers";
|
|
18
|
+
id: string;
|
|
19
|
+
email: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface EmailTemplateRef {
|
|
22
|
+
slug: string;
|
|
23
|
+
locale: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface EmailSendDedupeKeyInput {
|
|
26
|
+
recipientId: string;
|
|
27
|
+
templateSlug: string;
|
|
28
|
+
}
|
|
29
|
+
export type EmailSendDecision = {
|
|
30
|
+
outcome: "send";
|
|
31
|
+
} | {
|
|
32
|
+
outcome: "skip";
|
|
33
|
+
reason: EmailSendSkipReason;
|
|
34
|
+
existingMessageId: string | null;
|
|
35
|
+
};
|
|
36
|
+
export interface EmailMessageReadModel<Provider extends string = string> {
|
|
37
|
+
id: string;
|
|
38
|
+
channel: CommunicationChannel;
|
|
39
|
+
recipientId: string;
|
|
40
|
+
templateSlug: string;
|
|
41
|
+
status: EmailSendStatus;
|
|
42
|
+
provider: CommunicationProvider<Provider> | null;
|
|
43
|
+
providerMessageId: string | null;
|
|
44
|
+
skippedReason: EmailSendSkipReason | null;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
/** Host-selected schema inventory: managed and direct installations have different objects. */
|
|
3
|
+
export declare function createCommunicationsContribution(options: {
|
|
4
|
+
version: string;
|
|
5
|
+
requiredSchema: RequiredSchema;
|
|
6
|
+
routes?: Contribution["routes"];
|
|
7
|
+
handlers?: Contribution["handlers"];
|
|
8
|
+
schedules?: Contribution["schedules"];
|
|
9
|
+
requiredPorts?: readonly string[];
|
|
10
|
+
}): Contribution;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Host-selected schema inventory: managed and direct installations have different objects. */
|
|
2
|
+
export function createCommunicationsContribution(options) {
|
|
3
|
+
const routes = options.routes ?? [];
|
|
4
|
+
const handlers = options.handlers ?? [];
|
|
5
|
+
const schedules = options.schedules ?? [];
|
|
6
|
+
return {
|
|
7
|
+
manifest: {
|
|
8
|
+
name: "@openlup/platform", version: options.version, kind: "rail",
|
|
9
|
+
module: {
|
|
10
|
+
id: "communications", kind: "capability", provides: ["./communications", "./communications/contracts"],
|
|
11
|
+
dependsOn: [
|
|
12
|
+
{ module: "data", entries: ["./data"] },
|
|
13
|
+
{ module: "identity", entries: ["./identity"] },
|
|
14
|
+
{ module: "http", entries: ["./http"] },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
requiredSchema: options.requiredSchema,
|
|
18
|
+
emits: [], handles: handlers.map((handler) => handler.eventType),
|
|
19
|
+
schedules: schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })), routes: routes.map((route) => route.id),
|
|
20
|
+
requiredPorts: [...new Set(options.requiredPorts ?? [])], env: [],
|
|
21
|
+
},
|
|
22
|
+
handlers, schedules, routes,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface DeliveryPollCandidate {
|
|
2
|
+
id: string;
|
|
3
|
+
messageId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface DeliveryStatusRead {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
httpStatus: number;
|
|
8
|
+
recipientEmail: string | null;
|
|
9
|
+
}
|
|
10
|
+
/** The host maps its protocol to an opaque event type and terminal classification. */
|
|
11
|
+
export interface DeliveryPollObservation {
|
|
12
|
+
eventType: string;
|
|
13
|
+
terminal: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface DeliveryReconciliationPort {
|
|
16
|
+
findFrozenSends(input: {
|
|
17
|
+
now: string;
|
|
18
|
+
graceMinutes: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
}): Promise<DeliveryPollCandidate[]>;
|
|
21
|
+
applyPolledEvent(input: {
|
|
22
|
+
sendId: string;
|
|
23
|
+
messageId: string;
|
|
24
|
+
eventType: string;
|
|
25
|
+
eventAt: string;
|
|
26
|
+
recipientEmail: string | null;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
recordPollAttempt(input: {
|
|
29
|
+
sendId: string;
|
|
30
|
+
attemptedAt: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
recordSendNotFound(input: {
|
|
33
|
+
sendId: string;
|
|
34
|
+
threshold: number;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
abandoned: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export declare const DELIVERY_POLL_ABANDON_AFTER_NOT_FOUND = 3;
|
|
40
|
+
export interface DeliveryReadFailureBreakdown {
|
|
41
|
+
notFound: number;
|
|
42
|
+
authFailed: number;
|
|
43
|
+
network: number;
|
|
44
|
+
providerError: number;
|
|
45
|
+
other: number;
|
|
46
|
+
}
|
|
47
|
+
export interface DeliveryReconciliationResult {
|
|
48
|
+
checked: number;
|
|
49
|
+
reconciled: number;
|
|
50
|
+
stillPending: number;
|
|
51
|
+
terminalPolled: number;
|
|
52
|
+
failures: number;
|
|
53
|
+
abandoned: number;
|
|
54
|
+
failureBreakdown: DeliveryReadFailureBreakdown;
|
|
55
|
+
}
|
|
56
|
+
/** Sequential bounded polling around one host-owned atomic convergence writer. */
|
|
57
|
+
export declare function runDeliveryReconciliation<Read extends DeliveryStatusRead>(input: {
|
|
58
|
+
port: DeliveryReconciliationPort;
|
|
59
|
+
readEmail: (messageId: string) => Promise<Read>;
|
|
60
|
+
observationFor: (read: Read) => DeliveryPollObservation | null;
|
|
61
|
+
now: string;
|
|
62
|
+
graceMinutes: number;
|
|
63
|
+
limit: number;
|
|
64
|
+
}): Promise<DeliveryReconciliationResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const DELIVERY_POLL_ABANDON_AFTER_NOT_FOUND = 3;
|
|
2
|
+
function classifyReadFailure(breakdown, httpStatus) {
|
|
3
|
+
if (httpStatus === 404)
|
|
4
|
+
breakdown.notFound += 1;
|
|
5
|
+
else if (httpStatus === 401 || httpStatus === 403)
|
|
6
|
+
breakdown.authFailed += 1;
|
|
7
|
+
else if (httpStatus === 0)
|
|
8
|
+
breakdown.network += 1;
|
|
9
|
+
else if (httpStatus >= 500 && httpStatus <= 599)
|
|
10
|
+
breakdown.providerError += 1;
|
|
11
|
+
else
|
|
12
|
+
breakdown.other += 1;
|
|
13
|
+
}
|
|
14
|
+
/** Sequential bounded polling around one host-owned atomic convergence writer. */
|
|
15
|
+
export async function runDeliveryReconciliation(input) {
|
|
16
|
+
const result = {
|
|
17
|
+
checked: 0, reconciled: 0, stillPending: 0, terminalPolled: 0, failures: 0, abandoned: 0,
|
|
18
|
+
failureBreakdown: { notFound: 0, authFailed: 0, network: 0, providerError: 0, other: 0 },
|
|
19
|
+
};
|
|
20
|
+
const frozen = await input.port.findFrozenSends({ now: input.now, graceMinutes: input.graceMinutes, limit: input.limit });
|
|
21
|
+
for (const send of frozen) {
|
|
22
|
+
result.checked += 1;
|
|
23
|
+
const read = await input.readEmail(send.messageId);
|
|
24
|
+
if (read.httpStatus !== 404) {
|
|
25
|
+
try {
|
|
26
|
+
await input.port.recordPollAttempt({ sendId: send.id, attemptedAt: input.now });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Fairness bookkeeping failure must not discard observed delivery truth.
|
|
30
|
+
result.failures += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!read.ok) {
|
|
34
|
+
result.failures += 1;
|
|
35
|
+
classifyReadFailure(result.failureBreakdown, read.httpStatus);
|
|
36
|
+
if (read.httpStatus === 404) {
|
|
37
|
+
const { abandoned } = await input.port.recordSendNotFound({ sendId: send.id, threshold: DELIVERY_POLL_ABANDON_AFTER_NOT_FOUND });
|
|
38
|
+
if (abandoned)
|
|
39
|
+
result.abandoned += 1;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const observation = input.observationFor(read);
|
|
44
|
+
if (!observation) {
|
|
45
|
+
result.stillPending += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await input.port.applyPolledEvent({ sendId: send.id, messageId: send.messageId,
|
|
50
|
+
eventType: observation.eventType, eventAt: input.now, recipientEmail: read.recipientEmail });
|
|
51
|
+
result.reconciled += 1;
|
|
52
|
+
if (observation.terminal)
|
|
53
|
+
result.terminalPolled += 1;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
result.failures += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuthEmailTransportPort } from "./contracts/authEmail.js";
|
|
2
|
+
import type { EmailMessage, EmailSendResult, EmailTransport } from "./emailTransport.js";
|
|
3
|
+
export interface EmailEgressContext {
|
|
4
|
+
mode: "production" | "sandbox_direct" | "sandbox_sink";
|
|
5
|
+
reason: string;
|
|
6
|
+
intendedRecipient: string;
|
|
7
|
+
recipient: string;
|
|
8
|
+
/** Selected by the host; consumed only by its concrete sender, never logged. */
|
|
9
|
+
credential: string;
|
|
10
|
+
environmentTag: "production" | "non_production";
|
|
11
|
+
}
|
|
12
|
+
export type EmailEgressDecision = EmailEgressContext | {
|
|
13
|
+
mode: "drop";
|
|
14
|
+
reason: string;
|
|
15
|
+
} | {
|
|
16
|
+
mode: "blocked";
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export type EmailEgressPolicy = (intendedRecipient: string) => EmailEgressDecision | Promise<EmailEgressDecision>;
|
|
20
|
+
/**
|
|
21
|
+
* Host composition bridge for an implementation that already enforces egress
|
|
22
|
+
* internally. This is a trust boundary: its sender must itself refuse unknown
|
|
23
|
+
* policy before effects. New network adapters should use createGuardedEmailTransport.
|
|
24
|
+
* Neither a provider name nor an object property establishes admission.
|
|
25
|
+
*/
|
|
26
|
+
export declare function admitPolicyEnforcedEmailTransport<T extends EmailTransport>(transport: T): T;
|
|
27
|
+
/** Mandatory boundary for supported injected transports; raw objects fail closed. */
|
|
28
|
+
export declare function guardEmailTransport(transport: EmailTransport): EmailTransport;
|
|
29
|
+
/** Construct a network adapter whose sender sees only a validated host decision. */
|
|
30
|
+
export declare function createGuardedEmailTransport(input: {
|
|
31
|
+
providerKind: string;
|
|
32
|
+
policy: EmailEgressPolicy;
|
|
33
|
+
send: (message: EmailMessage, context: Readonly<EmailEgressContext>) => Promise<EmailSendResult>;
|
|
34
|
+
}): EmailTransport;
|
|
35
|
+
/** Preserve the Auth envelope/result mapping while retaining package admission. */
|
|
36
|
+
export declare function createAuthEmailTransport(transport: EmailTransport): AuthEmailTransportPort;
|
|
37
|
+
/** Raw Auth closures must compose through the admitted email transport bridge. */
|
|
38
|
+
export declare function guardAuthEmailTransport(transport: AuthEmailTransportPort): AuthEmailTransportPort;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const admitted = new WeakSet();
|
|
2
|
+
const admittedAuth = new WeakSet();
|
|
3
|
+
/**
|
|
4
|
+
* Host composition bridge for an implementation that already enforces egress
|
|
5
|
+
* internally. This is a trust boundary: its sender must itself refuse unknown
|
|
6
|
+
* policy before effects. New network adapters should use createGuardedEmailTransport.
|
|
7
|
+
* Neither a provider name nor an object property establishes admission.
|
|
8
|
+
*/
|
|
9
|
+
export function admitPolicyEnforcedEmailTransport(transport) {
|
|
10
|
+
admitted.add(transport);
|
|
11
|
+
return transport;
|
|
12
|
+
}
|
|
13
|
+
/** Mandatory boundary for supported injected transports; raw objects fail closed. */
|
|
14
|
+
export function guardEmailTransport(transport) {
|
|
15
|
+
if (admitted.has(transport))
|
|
16
|
+
return transport;
|
|
17
|
+
return { providerKind: transport.providerKind, send: async () => refused("email_egress_unadmitted") };
|
|
18
|
+
}
|
|
19
|
+
/** Construct a network adapter whose sender sees only a validated host decision. */
|
|
20
|
+
export function createGuardedEmailTransport(input) {
|
|
21
|
+
return admitPolicyEnforcedEmailTransport({
|
|
22
|
+
providerKind: input.providerKind,
|
|
23
|
+
async send(message) {
|
|
24
|
+
let decision;
|
|
25
|
+
try {
|
|
26
|
+
decision = await input.policy(message.to);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return refused("email_egress_policy_unavailable");
|
|
30
|
+
}
|
|
31
|
+
if (!validDecision(decision, message.to))
|
|
32
|
+
return refused("email_egress_policy_invalid");
|
|
33
|
+
if (decision.mode === "blocked")
|
|
34
|
+
return refused("email_egress_blocked", decision.reason);
|
|
35
|
+
if (decision.mode === "drop")
|
|
36
|
+
return {
|
|
37
|
+
outcome: { ok: true, resendId: null, httpStatus: 0, providerError: null, aborted: false,
|
|
38
|
+
suppressed: "drop", egressMode: "drop", egressReason: decision.reason },
|
|
39
|
+
providerResponse: { suppressed: "drop", egress_reason: decision.reason },
|
|
40
|
+
};
|
|
41
|
+
const context = Object.freeze({ ...decision });
|
|
42
|
+
const result = await input.send({ ...message, to: context.recipient }, context);
|
|
43
|
+
return { ...result, outcome: { ...result.outcome, egressMode: context.mode, egressReason: context.reason } };
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function validDecision(value, intended) {
|
|
48
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49
|
+
return false;
|
|
50
|
+
const d = value;
|
|
51
|
+
// Policy diagnostics are bounded codes, never arbitrary exception/secret text.
|
|
52
|
+
if (typeof d.reason !== "string" || !/^[a-z][a-z0-9_]{0,79}$/.test(d.reason))
|
|
53
|
+
return false;
|
|
54
|
+
if (d.mode === "drop" || d.mode === "blocked")
|
|
55
|
+
return Object.keys(d).every((key) => key === "mode" || key === "reason");
|
|
56
|
+
if (d.mode !== "production" && d.mode !== "sandbox_direct" && d.mode !== "sandbox_sink")
|
|
57
|
+
return false;
|
|
58
|
+
if (!Object.keys(d).every((key) => ["mode", "reason", "intendedRecipient", "recipient", "credential", "environmentTag"].includes(key)))
|
|
59
|
+
return false;
|
|
60
|
+
if (!mailbox(intended) || d.intendedRecipient !== intended || !mailbox(d.recipient))
|
|
61
|
+
return false;
|
|
62
|
+
if (d.mode !== "sandbox_sink" && d.recipient !== intended)
|
|
63
|
+
return false;
|
|
64
|
+
if (typeof d.credential !== "string" || !d.credential.trim())
|
|
65
|
+
return false;
|
|
66
|
+
return d.environmentTag === (d.mode === "production" ? "production" : "non_production");
|
|
67
|
+
}
|
|
68
|
+
function mailbox(value) {
|
|
69
|
+
return typeof value === "string" && /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/.test(value);
|
|
70
|
+
}
|
|
71
|
+
function refused(code, reason) {
|
|
72
|
+
return { outcome: { ok: false, resendId: null, httpStatus: 0, providerError: code, aborted: false,
|
|
73
|
+
...(reason ? { egressReason: reason } : {}) }, providerResponse: { egress_reason: code } };
|
|
74
|
+
}
|
|
75
|
+
/** Preserve the Auth envelope/result mapping while retaining package admission. */
|
|
76
|
+
export function createAuthEmailTransport(transport) {
|
|
77
|
+
const guarded = guardEmailTransport(transport);
|
|
78
|
+
const auth = {
|
|
79
|
+
providerKind: transport.providerKind,
|
|
80
|
+
async send(message) {
|
|
81
|
+
const result = await guarded.send({ from: message.sender, to: message.recipient,
|
|
82
|
+
subject: message.subject, html: message.html, text: message.text });
|
|
83
|
+
return { outcome: { ...result.outcome, messageId: result.outcome.resendId }, providerResponse: result.providerResponse };
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
admittedAuth.add(auth);
|
|
87
|
+
return auth;
|
|
88
|
+
}
|
|
89
|
+
/** Raw Auth closures must compose through the admitted email transport bridge. */
|
|
90
|
+
export function guardAuthEmailTransport(transport) {
|
|
91
|
+
if (admittedAuth.has(transport))
|
|
92
|
+
return transport;
|
|
93
|
+
return {
|
|
94
|
+
providerKind: transport.providerKind,
|
|
95
|
+
async send() {
|
|
96
|
+
const result = refused("email_egress_unadmitted");
|
|
97
|
+
return { outcome: { ...result.outcome, messageId: null }, providerResponse: result.providerResponse };
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EmailHealthMetrics } from "./emailHealthMetricsPort.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure email-health assessment layer: env-derived thresholds plus the
|
|
4
|
+
* healthy/unhealthy verdict over the delivery metrics. Extracted from
|
|
5
|
+
* `api/_cron/emailHealthWatchdogJob.ts` (which sat at the 300-LOC source cap)
|
|
6
|
+
* so the incident-driven `permanent_delivery_failure` breach could land here
|
|
7
|
+
* with unit coverage and no database in the loop.
|
|
8
|
+
*/
|
|
9
|
+
type Env = Record<string, string | undefined>;
|
|
10
|
+
export interface EmailHealthThresholds {
|
|
11
|
+
windowMinutes: number;
|
|
12
|
+
stuckMinutes: number;
|
|
13
|
+
minAttemptsForRatio: number;
|
|
14
|
+
maxFailureRatio: number;
|
|
15
|
+
maxStuck: number;
|
|
16
|
+
/**
|
|
17
|
+
* Absolute ceiling on in-window deliveries that ended `status='failed'`
|
|
18
|
+
* (handler-final loss of a customer notification: outbox discard,
|
|
19
|
+
* max-attempts exhaustion). Default 0 — a single lost email breaches.
|
|
20
|
+
* Bounces/complaints are separate statuses and stay ratio-only.
|
|
21
|
+
*/
|
|
22
|
+
maxPermanentFailures: number;
|
|
23
|
+
}
|
|
24
|
+
export interface EmailHealthBreach {
|
|
25
|
+
code: string;
|
|
26
|
+
detail: string;
|
|
27
|
+
}
|
|
28
|
+
export interface EmailHealthAssessment {
|
|
29
|
+
healthy: boolean;
|
|
30
|
+
failureRatio: number;
|
|
31
|
+
breaches: EmailHealthBreach[];
|
|
32
|
+
}
|
|
33
|
+
export declare function readEmailHealthThresholds(env: Env): EmailHealthThresholds;
|
|
34
|
+
/**
|
|
35
|
+
* Pure decision: turn raw counts into a healthy/unhealthy verdict. Kept side
|
|
36
|
+
* effect-free so the thresholds logic is unit-tested without a database.
|
|
37
|
+
*/
|
|
38
|
+
export declare function evaluateEmailHealth(metrics: EmailHealthMetrics, thresholds: EmailHealthThresholds): EmailHealthAssessment;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function readEmailHealthThresholds(env) {
|
|
2
|
+
return {
|
|
3
|
+
windowMinutes: positiveInt(env.EMAIL_HEALTH_WINDOW_MINUTES, 60),
|
|
4
|
+
stuckMinutes: positiveInt(env.EMAIL_HEALTH_STUCK_MINUTES, 30),
|
|
5
|
+
minAttemptsForRatio: positiveInt(env.EMAIL_HEALTH_MIN_ATTEMPTS_FOR_RATIO, 5),
|
|
6
|
+
maxFailureRatio: clampRatio(env.EMAIL_HEALTH_MAX_FAILURE_RATIO, 0.5),
|
|
7
|
+
maxStuck: nonNegativeInt(env.EMAIL_HEALTH_MAX_STUCK, 0),
|
|
8
|
+
maxPermanentFailures: nonNegativeInt(env.EMAIL_HEALTH_MAX_PERMANENT_FAILURES, 0),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Pure decision: turn raw counts into a healthy/unhealthy verdict. Kept side
|
|
13
|
+
* effect-free so the thresholds logic is unit-tested without a database.
|
|
14
|
+
*/
|
|
15
|
+
export function evaluateEmailHealth(metrics, thresholds) {
|
|
16
|
+
const failureRatio = metrics.attempted > 0 ? metrics.failed / metrics.attempted : 0;
|
|
17
|
+
const breaches = [];
|
|
18
|
+
if (metrics.attempted >= thresholds.minAttemptsForRatio && failureRatio > thresholds.maxFailureRatio) {
|
|
19
|
+
breaches.push({
|
|
20
|
+
code: "failure_ratio",
|
|
21
|
+
detail: `${metrics.failed}/${metrics.attempted} (${(failureRatio * 100).toFixed(1)}%) > ${(thresholds.maxFailureRatio * 100).toFixed(1)}%`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Absolute, ratio-independent: one terminally-failed delivery is one
|
|
25
|
+
// customer notification we finally did not send. Incident 2026-07-24: a
|
|
26
|
+
// single discarded payment-recovery email stayed invisible because the
|
|
27
|
+
// ratio breach needs minAttemptsForRatio sends in the window.
|
|
28
|
+
if (metrics.permanentFailures > thresholds.maxPermanentFailures) {
|
|
29
|
+
breaches.push({
|
|
30
|
+
code: "permanent_delivery_failure",
|
|
31
|
+
detail: `${metrics.permanentFailures} delivery(ies) terminally failed > ${thresholds.maxPermanentFailures}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (metrics.stuckProcessing > thresholds.maxStuck) {
|
|
35
|
+
breaches.push({
|
|
36
|
+
code: "stuck_processing",
|
|
37
|
+
detail: `${metrics.stuckProcessing} email(s) stuck in 'processing' > ${thresholds.stuckMinutes}m`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (metrics.stuckScheduled > thresholds.maxStuck) {
|
|
41
|
+
breaches.push({
|
|
42
|
+
code: "stuck_scheduled",
|
|
43
|
+
detail: `${metrics.stuckScheduled} scheduled email(s) overdue and undispatched`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { healthy: breaches.length === 0, failureRatio, breaches };
|
|
47
|
+
}
|
|
48
|
+
function positiveInt(raw, fallback) {
|
|
49
|
+
const n = Number(raw);
|
|
50
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
51
|
+
}
|
|
52
|
+
function nonNegativeInt(raw, fallback) {
|
|
53
|
+
const n = Number(raw);
|
|
54
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
55
|
+
}
|
|
56
|
+
function clampRatio(raw, fallback) {
|
|
57
|
+
const n = Number(raw);
|
|
58
|
+
return Number.isFinite(n) && n >= 0 && n <= 1 ? n : fallback;
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface EmailHealthMetrics {
|
|
2
|
+
attempted: number;
|
|
3
|
+
sent: number;
|
|
4
|
+
failed: number;
|
|
5
|
+
delayed: number;
|
|
6
|
+
/**
|
|
7
|
+
* In-window rows that ended `status='failed'` — handler-final loss of a
|
|
8
|
+
* customer notification (outbox discard / max-attempts). Distinct from
|
|
9
|
+
* `failed` above, which also counts bounces/complaints/missed for the
|
|
10
|
+
* ratio signal.
|
|
11
|
+
*/
|
|
12
|
+
permanentFailures: number;
|
|
13
|
+
/** `processing` rows whose last transition is older than the stuck threshold. */
|
|
14
|
+
stuckProcessing: number;
|
|
15
|
+
/** `planned`/`queued` rows past their `expected_send_at` but still not sent. */
|
|
16
|
+
stuckScheduled: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface EmailNotificationControlPort {
|
|
2
|
+
isEnabled(slug: string, signal: AbortSignal): Promise<boolean>;
|
|
3
|
+
}
|
|
4
|
+
export interface EmailNotificationControlClient {
|
|
5
|
+
from(table: string): EmailNotificationControlQueryBuilder;
|
|
6
|
+
}
|
|
7
|
+
interface EmailNotificationControlQueryBuilder {
|
|
8
|
+
select(columns: string): EmailNotificationControlQueryBuilder;
|
|
9
|
+
eq(column: string, value: unknown): EmailNotificationControlQueryBuilder;
|
|
10
|
+
maybeSingle(): PromiseLike<{
|
|
11
|
+
data: unknown;
|
|
12
|
+
error: {
|
|
13
|
+
code?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
} | null;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createEmailNotificationControlPort(client: EmailNotificationControlClient, createPolicy: (client: EmailNotificationControlClient) => {
|
|
19
|
+
shouldDeliver(input: {
|
|
20
|
+
slug: string;
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
allow: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
}): EmailNotificationControlPort;
|
|
26
|
+
export declare const createSupabaseEmailNotificationControlPort: typeof createEmailNotificationControlPort;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createEmailNotificationControlPort(client, createPolicy) {
|
|
2
|
+
const policy = createPolicy(client);
|
|
3
|
+
return {
|
|
4
|
+
isEnabled(slug, signal) {
|
|
5
|
+
return policy.shouldDeliver({ slug, signal }).then((decision) => decision.allow);
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
// Compatibility alias for the pre-neutralization lifecycle consumer.
|
|
10
|
+
export const createSupabaseEmailNotificationControlPort = createEmailNotificationControlPort;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** An authenticated, decoded, environment-admitted observation. Evidence is opaque. */
|
|
2
|
+
export interface EmailObservation {
|
|
3
|
+
eventType: string;
|
|
4
|
+
eventAt: string;
|
|
5
|
+
metadata: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface EmailObservationPort {
|
|
8
|
+
findSend(messageId: string): Promise<{
|
|
9
|
+
id: string;
|
|
10
|
+
} | null>;
|
|
11
|
+
applyObservation(observation: EmailObservation & {
|
|
12
|
+
sendId: string;
|
|
13
|
+
messageId: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
recordAttempt(attempt: EmailObservationDisposition): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export type EmailObservationDisposition = {
|
|
18
|
+
outcome: "send_not_found";
|
|
19
|
+
} | {
|
|
20
|
+
outcome: "processed";
|
|
21
|
+
sendId: string;
|
|
22
|
+
};
|
|
23
|
+
/** Lookup/apply failures propagate; attempt telemetry cannot overturn authoritative success. */
|
|
24
|
+
export declare function processEmailObservation(input: {
|
|
25
|
+
port: EmailObservationPort;
|
|
26
|
+
messageId: string;
|
|
27
|
+
observation: () => EmailObservation;
|
|
28
|
+
}): Promise<EmailObservationDisposition>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Lookup/apply failures propagate; attempt telemetry cannot overturn authoritative success. */
|
|
2
|
+
export async function processEmailObservation(input) {
|
|
3
|
+
const send = await input.port.findSend(input.messageId);
|
|
4
|
+
let disposition;
|
|
5
|
+
if (!send) {
|
|
6
|
+
disposition = { outcome: "send_not_found" };
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
await input.port.applyObservation({
|
|
10
|
+
sendId: send.id,
|
|
11
|
+
messageId: input.messageId,
|
|
12
|
+
...input.observation(),
|
|
13
|
+
});
|
|
14
|
+
disposition = { outcome: "processed", sendId: send.id };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
await input.port.recordAttempt(disposition);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Best-effort telemetry is separate from durable convergence.
|
|
21
|
+
}
|
|
22
|
+
return disposition;
|
|
23
|
+
}
|