@openlup/platform 0.16.0 → 0.18.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 (773) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +564 -6
  3. package/dist/catalog/activeSkuSelection.d.ts +2 -0
  4. package/dist/catalog/activeSkuSelection.js +20 -0
  5. package/dist/catalog/adminBindings.d.ts +72 -0
  6. package/dist/catalog/adminBindings.js +1 -0
  7. package/dist/catalog/adminBundleHandler.d.ts +26 -0
  8. package/dist/catalog/adminBundleHandler.js +111 -0
  9. package/dist/catalog/adminBundleReadHandler.d.ts +20 -0
  10. package/dist/catalog/adminBundleReadHandler.js +185 -0
  11. package/dist/catalog/adminBundleWritePort.d.ts +85 -0
  12. package/dist/catalog/adminBundleWritePort.js +1 -0
  13. package/dist/catalog/bundleAvailability.d.ts +63 -0
  14. package/dist/catalog/bundleAvailability.js +78 -0
  15. package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
  16. package/dist/catalog/bundleAvailabilityService.js +89 -0
  17. package/dist/catalog/bundleBindings.d.ts +33 -0
  18. package/dist/catalog/bundleBindings.js +1 -0
  19. package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
  20. package/dist/catalog/bundleCatalogQueries.js +84 -0
  21. package/dist/catalog/bundleCatalogReadPort.d.ts +166 -0
  22. package/dist/catalog/bundleCatalogReadPort.js +36 -0
  23. package/dist/catalog/bundleCatalogStore.d.ts +3 -0
  24. package/dist/catalog/bundleCatalogStore.js +152 -0
  25. package/dist/catalog/bundleRuleCodes.d.ts +41 -0
  26. package/dist/catalog/bundleRuleCodes.js +110 -0
  27. package/dist/catalog/bundleRules.d.ts +69 -0
  28. package/dist/catalog/bundleRules.js +102 -0
  29. package/dist/catalog/catalogDraftStore.d.ts +4 -0
  30. package/dist/catalog/catalogDraftStore.js +63 -0
  31. package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
  32. package/dist/catalog/catalogSkuEnvelope.js +142 -0
  33. package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
  34. package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
  35. package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
  36. package/dist/catalog/contracts/bundleAdmin.js +1 -0
  37. package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
  38. package/dist/catalog/contracts/bundleAvailability.js +1 -0
  39. package/dist/catalog/contracts/bundleRead.d.ts +223 -0
  40. package/dist/catalog/contracts/bundleRead.js +1 -0
  41. package/dist/catalog/contracts/draft.d.ts +79 -0
  42. package/dist/catalog/contracts/draft.js +47 -0
  43. package/dist/catalog/contracts/foundation.d.ts +78 -0
  44. package/dist/catalog/contracts/foundation.js +53 -0
  45. package/dist/catalog/contracts/index.d.ts +14 -0
  46. package/dist/catalog/contracts/index.js +6 -0
  47. package/dist/catalog/contracts/lifecycle.d.ts +77 -0
  48. package/dist/catalog/contracts/lifecycle.js +1 -0
  49. package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
  50. package/dist/catalog/contracts/productMediaContracts.js +149 -0
  51. package/dist/catalog/contracts/productTypes.d.ts +54 -0
  52. package/dist/catalog/contracts/productTypes.js +16 -0
  53. package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
  54. package/dist/catalog/contracts/resolveProductMedia.js +78 -0
  55. package/dist/catalog/contracts/sellable.d.ts +23 -0
  56. package/dist/catalog/contracts/sellable.js +1 -0
  57. package/dist/catalog/draftHandler.d.ts +25 -0
  58. package/dist/catalog/draftHandler.js +118 -0
  59. package/dist/catalog/draftPorts.d.ts +18 -0
  60. package/dist/catalog/draftPorts.js +8 -0
  61. package/dist/catalog/draftSchemas.d.ts +423 -0
  62. package/dist/catalog/draftSchemas.js +89 -0
  63. package/dist/catalog/draftValidation.d.ts +21 -0
  64. package/dist/catalog/draftValidation.js +125 -0
  65. package/dist/catalog/foundationSchemas.d.ts +98 -0
  66. package/dist/catalog/foundationSchemas.js +110 -0
  67. package/dist/catalog/index.d.ts +43 -0
  68. package/dist/catalog/index.js +25 -0
  69. package/dist/catalog/mediaSourcePack.d.ts +51 -0
  70. package/dist/catalog/mediaSourcePack.js +247 -0
  71. package/dist/catalog/productTypeSchemas.d.ts +46 -0
  72. package/dist/catalog/productTypeSchemas.js +72 -0
  73. package/dist/catalog/publicationPorts.d.ts +42 -0
  74. package/dist/catalog/publicationPorts.js +1 -0
  75. package/dist/catalog/readPorts.d.ts +51 -0
  76. package/dist/catalog/readPorts.js +1 -0
  77. package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
  78. package/dist/catalog/referenceCatalogHandler.js +27 -0
  79. package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
  80. package/dist/catalog/resolvedSkuProjection.js +60 -0
  81. package/dist/catalog/sellableBundleHandler.d.ts +46 -0
  82. package/dist/catalog/sellableBundleHandler.js +84 -0
  83. package/dist/catalog/sellableSchemas.d.ts +46 -0
  84. package/dist/catalog/sellableSchemas.js +27 -0
  85. package/dist/catalog/sql.d.ts +46 -0
  86. package/dist/catalog/sql.js +13 -0
  87. package/dist/catalog/validation.d.ts +2 -0
  88. package/dist/catalog/validation.js +3 -0
  89. package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
  90. package/dist/communications/accountingDocumentDelivery.js +33 -0
  91. package/dist/communications/authEmailError.d.ts +5 -0
  92. package/dist/communications/authEmailError.js +8 -0
  93. package/dist/communications/authEmailPorts.d.ts +2 -0
  94. package/dist/communications/authEmailPorts.js +1 -0
  95. package/dist/communications/authSendEmailHook.d.ts +35 -0
  96. package/dist/communications/authSendEmailHook.js +250 -0
  97. package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
  98. package/dist/communications/capturedTransactionalDelivery.js +14 -0
  99. package/dist/communications/communicationSync.d.ts +11 -0
  100. package/dist/communications/communicationSync.js +79 -0
  101. package/dist/communications/communicationSyncWorker.d.ts +68 -0
  102. package/dist/communications/communicationSyncWorker.js +218 -0
  103. package/dist/communications/consentPolicy.d.ts +19 -0
  104. package/dist/communications/consentPolicy.js +43 -0
  105. package/dist/communications/consentPurposeRegistry.d.ts +4 -0
  106. package/dist/communications/consentPurposeRegistry.js +24 -0
  107. package/dist/communications/contracts/abandonedCart.d.ts +27 -0
  108. package/dist/communications/contracts/abandonedCart.js +25 -0
  109. package/dist/communications/contracts/authEmail.d.ts +62 -0
  110. package/dist/communications/contracts/authEmail.js +1 -0
  111. package/dist/communications/contracts/backInStock.d.ts +28 -0
  112. package/dist/communications/contracts/backInStock.js +28 -0
  113. package/dist/communications/contracts/blocks.d.ts +24 -0
  114. package/dist/communications/contracts/blocks.js +23 -0
  115. package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
  116. package/dist/communications/contracts/checkoutExpired.js +25 -0
  117. package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
  118. package/dist/communications/contracts/checkoutRecovery.js +37 -0
  119. package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
  120. package/dist/communications/contracts/commerceEmailContent.js +1 -0
  121. package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
  122. package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
  123. package/dist/communications/contracts/consent.d.ts +12 -0
  124. package/dist/communications/contracts/consent.js +1 -0
  125. package/dist/communications/contracts/email.d.ts +182 -0
  126. package/dist/communications/contracts/email.js +1 -0
  127. package/dist/communications/contracts/guideCard.d.ts +3 -0
  128. package/dist/communications/contracts/guideCard.js +26 -0
  129. package/dist/communications/contracts/index.d.ts +52 -0
  130. package/dist/communications/contracts/index.js +42 -0
  131. package/dist/communications/contracts/inquiryContent.d.ts +32 -0
  132. package/dist/communications/contracts/inquiryContent.js +33 -0
  133. package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
  134. package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
  135. package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
  136. package/dist/communications/contracts/marketingAutomation.js +1 -0
  137. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  138. package/dist/communications/contracts/marketingSync.js +1 -0
  139. package/dist/communications/contracts/newsletter.d.ts +63 -0
  140. package/dist/communications/contracts/newsletter.js +1 -0
  141. package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
  142. package/dist/communications/contracts/newsletterWebhook.js +1 -0
  143. package/dist/communications/contracts/optOutContent.d.ts +11 -0
  144. package/dist/communications/contracts/optOutContent.js +17 -0
  145. package/dist/communications/contracts/orderCanceled.d.ts +25 -0
  146. package/dist/communications/contracts/orderCanceled.js +27 -0
  147. package/dist/communications/contracts/orderDraft.d.ts +46 -0
  148. package/dist/communications/contracts/orderDraft.js +46 -0
  149. package/dist/communications/contracts/orderPaid.d.ts +53 -0
  150. package/dist/communications/contracts/orderPaid.js +71 -0
  151. package/dist/communications/contracts/orderRefunded.d.ts +24 -0
  152. package/dist/communications/contracts/orderRefunded.js +27 -0
  153. package/dist/communications/contracts/paymentFailed.d.ts +24 -0
  154. package/dist/communications/contracts/paymentFailed.js +24 -0
  155. package/dist/communications/contracts/publicIntake.d.ts +49 -0
  156. package/dist/communications/contracts/publicIntake.js +1 -0
  157. package/dist/communications/contracts/render.d.ts +2 -0
  158. package/dist/communications/contracts/render.js +229 -0
  159. package/dist/communications/contracts/reorderReminder.d.ts +24 -0
  160. package/dist/communications/contracts/reorderReminder.js +24 -0
  161. package/dist/communications/contracts/returnEmails.d.ts +25 -0
  162. package/dist/communications/contracts/returnEmails.js +62 -0
  163. package/dist/communications/contracts/reviewEffects.d.ts +26 -0
  164. package/dist/communications/contracts/reviewEffects.js +25 -0
  165. package/dist/communications/contracts/reviewRequest.d.ts +26 -0
  166. package/dist/communications/contracts/reviewRequest.js +24 -0
  167. package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
  168. package/dist/communications/contracts/roleNotificationContent.js +46 -0
  169. package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
  170. package/dist/communications/contracts/shipmentDelivered.js +24 -0
  171. package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
  172. package/dist/communications/contracts/shipmentDispatched.js +35 -0
  173. package/dist/communications/contracts/shipmentException.d.ts +21 -0
  174. package/dist/communications/contracts/shipmentException.js +26 -0
  175. package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
  176. package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
  177. package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
  178. package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
  179. package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
  180. package/dist/communications/contracts/subscriptionCancelled.js +25 -0
  181. package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
  182. package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
  183. package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
  184. package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
  185. package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
  186. package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
  187. package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
  188. package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
  189. package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
  190. package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
  191. package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
  192. package/dist/communications/contracts/subscriptionPaused.js +25 -0
  193. package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
  194. package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
  195. package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
  196. package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
  197. package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
  198. package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
  199. package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
  200. package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
  201. package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
  202. package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
  203. package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
  204. package/dist/communications/contracts/subscriptionResumed.js +25 -0
  205. package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
  206. package/dist/communications/contracts/subscriptionWelcome.js +45 -0
  207. package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
  208. package/dist/communications/contracts/subscriptionWinback.js +28 -0
  209. package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
  210. package/dist/communications/contracts/transactionalDelivery.js +1 -0
  211. package/dist/communications/contracts/types.d.ts +46 -0
  212. package/dist/communications/contracts/types.js +1 -0
  213. package/dist/communications/contribution.d.ts +10 -0
  214. package/dist/communications/contribution.js +24 -0
  215. package/dist/communications/deliveryReconciliation.d.ts +64 -0
  216. package/dist/communications/deliveryReconciliation.js +60 -0
  217. package/dist/communications/emailEgress.d.ts +38 -0
  218. package/dist/communications/emailEgress.js +100 -0
  219. package/dist/communications/emailHealthAssessment.d.ts +39 -0
  220. package/dist/communications/emailHealthAssessment.js +59 -0
  221. package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
  222. package/dist/communications/emailHealthMetricsPort.js +1 -0
  223. package/dist/communications/emailNotificationControl.d.ts +27 -0
  224. package/dist/communications/emailNotificationControl.js +10 -0
  225. package/dist/communications/emailObservation.d.ts +28 -0
  226. package/dist/communications/emailObservation.js +23 -0
  227. package/dist/communications/emailTransport.d.ts +82 -0
  228. package/dist/communications/emailTransport.js +95 -0
  229. package/dist/communications/errors.d.ts +15 -0
  230. package/dist/communications/errors.js +30 -0
  231. package/dist/communications/index.d.ts +47 -0
  232. package/dist/communications/index.js +38 -0
  233. package/dist/communications/integrationEventSignature.d.ts +15 -0
  234. package/dist/communications/integrationEventSignature.js +37 -0
  235. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  236. package/dist/communications/klaviyo/consentPush.js +19 -0
  237. package/dist/communications/klaviyo/index.d.ts +110 -0
  238. package/dist/communications/klaviyo/index.js +860 -0
  239. package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
  240. package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
  241. package/dist/communications/nativeOperations.d.ts +79 -0
  242. package/dist/communications/nativeOperations.js +3 -0
  243. package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
  244. package/dist/communications/newsletterIntegrationContracts.js +128 -0
  245. package/dist/communications/newsletterWebhookPort.d.ts +11 -0
  246. package/dist/communications/newsletterWebhookPort.js +84 -0
  247. package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
  248. package/dist/communications/newsletterWebhookProcessing.js +71 -0
  249. package/dist/communications/originPolicy.d.ts +40 -0
  250. package/dist/communications/originPolicy.js +100 -0
  251. package/dist/communications/postgresCommunicationSync.d.ts +79 -0
  252. package/dist/communications/postgresCommunicationSync.js +111 -0
  253. package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
  254. package/dist/communications/postgresConsentEvaluator.js +43 -0
  255. package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
  256. package/dist/communications/postgresDeliveryReconciliation.js +113 -0
  257. package/dist/communications/postgresNativeOperations.d.ts +13 -0
  258. package/dist/communications/postgresNativeOperations.js +63 -0
  259. package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
  260. package/dist/communications/postgresNotificationRecipients.js +60 -0
  261. package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
  262. package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
  263. package/dist/communications/preferencesToken.d.ts +10 -0
  264. package/dist/communications/preferencesToken.js +40 -0
  265. package/dist/communications/publicPreferenceToken.d.ts +24 -0
  266. package/dist/communications/publicPreferenceToken.js +53 -0
  267. package/dist/communications/resend/index.d.ts +40 -0
  268. package/dist/communications/resend/index.js +310 -0
  269. package/dist/communications/shipmentNotifications.d.ts +34 -0
  270. package/dist/communications/shipmentNotifications.js +61 -0
  271. package/dist/communications/sql.d.ts +26 -0
  272. package/dist/communications/sql.js +7 -0
  273. package/dist/communications/standardWebhooks.d.ts +14 -0
  274. package/dist/communications/standardWebhooks.js +73 -0
  275. package/dist/communications/status.d.ts +11 -0
  276. package/dist/communications/status.js +36 -0
  277. package/dist/communications/transactionalDelivery.d.ts +19 -0
  278. package/dist/communications/transactionalDelivery.js +96 -0
  279. package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
  280. package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
  281. package/dist/communications/unsubscribeToken.d.ts +77 -0
  282. package/dist/communications/unsubscribeToken.js +150 -0
  283. package/dist/communications/validation.d.ts +2 -0
  284. package/dist/communications/validation.js +3 -0
  285. package/dist/composition/catalogContribution.d.ts +18 -0
  286. package/dist/composition/catalogContribution.js +21 -0
  287. package/dist/composition/index.d.ts +1 -0
  288. package/dist/composition/index.js +1 -0
  289. package/dist/http/index.d.ts +1 -0
  290. package/dist/http/index.js +1 -0
  291. package/dist/http/webhookSignature.d.ts +42 -0
  292. package/dist/http/webhookSignature.js +86 -0
  293. package/dist/identity/customerContracts.d.ts +2 -2
  294. package/dist/inventory/adminInventory.d.ts +13 -0
  295. package/dist/inventory/adminInventory.js +214 -0
  296. package/dist/inventory/contracts/forecast.d.ts +86 -0
  297. package/dist/inventory/contracts/forecast.js +1 -0
  298. package/dist/inventory/contracts/index.d.ts +4 -0
  299. package/dist/inventory/contracts/index.js +1 -0
  300. package/dist/inventory/contracts/inventory.d.ts +157 -0
  301. package/dist/inventory/contracts/inventory.js +1 -0
  302. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  303. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  304. package/dist/inventory/contracts/reservation.d.ts +18 -0
  305. package/dist/inventory/contracts/reservation.js +1 -0
  306. package/dist/inventory/forecast.d.ts +24 -0
  307. package/dist/inventory/forecast.js +200 -0
  308. package/dist/inventory/index.d.ts +13 -0
  309. package/dist/inventory/index.js +10 -0
  310. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  311. package/dist/inventory/inventoryHandlers.js +151 -0
  312. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  313. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  314. package/dist/inventory/ports.d.ts +21 -0
  315. package/dist/inventory/ports.js +16 -0
  316. package/dist/inventory/renewalReservation.d.ts +27 -0
  317. package/dist/inventory/renewalReservation.js +61 -0
  318. package/dist/inventory/reservationPolicy.d.ts +6 -0
  319. package/dist/inventory/reservationPolicy.js +19 -0
  320. package/dist/inventory/schemas.d.ts +137 -0
  321. package/dist/inventory/schemas.js +112 -0
  322. package/dist/inventory/sql.d.ts +14 -0
  323. package/dist/inventory/sql.js +5 -0
  324. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  325. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  326. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  327. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  328. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  329. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  330. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  331. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  332. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  333. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  334. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  335. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  336. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  337. package/dist/pricing/acquisitionReadiness.js +117 -0
  338. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  339. package/dist/pricing/acquisitionTerms.js +126 -0
  340. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  341. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  342. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  343. package/dist/pricing/catalogFactsProvenance.js +64 -0
  344. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  345. package/dist/pricing/catalogFactsSchemas.js +58 -0
  346. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  347. package/dist/pricing/commerceOfferPricing.js +139 -0
  348. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  349. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  350. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  351. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  352. package/dist/pricing/commercePriceAuthority.d.ts +26 -0
  353. package/dist/pricing/commercePriceAuthority.js +79 -0
  354. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  355. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  356. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  357. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  358. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  359. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  360. package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
  361. package/dist/pricing/commerceQuoteHandler.js +110 -0
  362. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  363. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  364. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  365. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  366. package/dist/pricing/contracts/authority.d.ts +27 -0
  367. package/dist/pricing/contracts/authority.js +1 -0
  368. package/dist/pricing/contracts/evidence.d.ts +33 -0
  369. package/dist/pricing/contracts/evidence.js +1 -0
  370. package/dist/pricing/contracts/index.d.ts +17 -0
  371. package/dist/pricing/contracts/index.js +7 -0
  372. package/dist/pricing/contracts/money.d.ts +27 -0
  373. package/dist/pricing/contracts/money.js +1 -0
  374. package/dist/pricing/contracts/offer.d.ts +52 -0
  375. package/dist/pricing/contracts/offer.js +4 -0
  376. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  377. package/dist/pricing/contracts/offerPolicy.js +5 -0
  378. package/dist/pricing/contracts/policy.d.ts +22 -0
  379. package/dist/pricing/contracts/policy.js +3 -0
  380. package/dist/pricing/contracts/provenance.d.ts +24 -0
  381. package/dist/pricing/contracts/provenance.js +2 -0
  382. package/dist/pricing/contracts/quote.d.ts +94 -0
  383. package/dist/pricing/contracts/quote.js +1 -0
  384. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  385. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  386. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
  387. package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
  388. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  389. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  390. package/dist/pricing/index.d.ts +51 -0
  391. package/dist/pricing/index.js +36 -0
  392. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  393. package/dist/pricing/offerPolicyReadiness.js +79 -0
  394. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  395. package/dist/pricing/offerPolicySchemas.js +15 -0
  396. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  397. package/dist/pricing/offerPricingSchemas.js +65 -0
  398. package/dist/pricing/ports.d.ts +13 -0
  399. package/dist/pricing/ports.js +9 -0
  400. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  401. package/dist/pricing/pricingPolicyToken.js +84 -0
  402. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  403. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  404. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  405. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  406. package/dist/pricing/quotePorts.d.ts +30 -0
  407. package/dist/pricing/quotePorts.js +20 -0
  408. package/dist/pricing/quotePrimitives.d.ts +13 -0
  409. package/dist/pricing/quotePrimitives.js +18 -0
  410. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  411. package/dist/pricing/quotePromotionEvidence.js +73 -0
  412. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  413. package/dist/pricing/quoteSchemas.js +196 -0
  414. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  415. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  416. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  417. package/dist/pricing/taxProfileGuard.js +24 -0
  418. package/dist/pricing/validation.d.ts +2 -0
  419. package/dist/pricing/validation.js +3 -0
  420. package/dist/promotions/adminBindings.d.ts +72 -0
  421. package/dist/promotions/adminBindings.js +1 -0
  422. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  423. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  424. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  425. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  426. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  427. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  428. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  429. package/dist/promotions/adminPromotionsContracts.js +99 -0
  430. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  431. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  432. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  433. package/dist/promotions/adminPromotionsHandler.js +47 -0
  434. package/dist/promotions/contracts/index.d.ts +387 -0
  435. package/dist/promotions/contracts/index.js +1 -0
  436. package/dist/promotions/index.d.ts +22 -0
  437. package/dist/promotions/index.js +21 -0
  438. package/dist/promotions/promoDataPort.d.ts +47 -0
  439. package/dist/promotions/promoDataPort.js +14 -0
  440. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  441. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  442. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  443. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  444. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  445. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  446. package/dist/promotions/promotionClaims.d.ts +5 -0
  447. package/dist/promotions/promotionClaims.js +57 -0
  448. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  449. package/dist/promotions/promotionCodePreview.js +154 -0
  450. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  451. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  452. package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
  453. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  454. package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
  455. package/dist/promotions/promotionQuoteBinding.js +85 -0
  456. package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
  457. package/dist/promotions/quoteAutomaticPromotions.js +36 -0
  458. package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
  459. package/dist/promotions/quotePromoDiscounts.js +140 -0
  460. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  461. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  462. package/dist/promotions/quotePromotionV2.d.ts +29 -0
  463. package/dist/promotions/quotePromotionV2.js +174 -0
  464. package/dist/promotions/sql.d.ts +14 -0
  465. package/dist/promotions/sql.js +5 -0
  466. package/dist/promotions/validation.d.ts +2 -0
  467. package/dist/promotions/validation.js +3 -0
  468. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  469. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  470. package/dist/shipping/addressCanonical.d.ts +2 -0
  471. package/dist/shipping/addressCanonical.js +26 -0
  472. package/dist/shipping/addressPorts.d.ts +39 -0
  473. package/dist/shipping/addressPorts.js +1 -0
  474. package/dist/shipping/addressSchemas.d.ts +30 -0
  475. package/dist/shipping/addressSchemas.js +1 -0
  476. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  477. package/dist/shipping/adminShippingRateHandler.js +64 -0
  478. package/dist/shipping/contracts/address.d.ts +144 -0
  479. package/dist/shipping/contracts/address.js +1 -0
  480. package/dist/shipping/contracts/delivery.d.ts +110 -0
  481. package/dist/shipping/contracts/delivery.js +1 -0
  482. package/dist/shipping/contracts/index.d.ts +3 -0
  483. package/dist/shipping/contracts/index.js +1 -0
  484. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  485. package/dist/shipping/contracts/shippingRate.js +1 -0
  486. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  487. package/dist/shipping/customerAddressesHandler.js +80 -0
  488. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  489. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  490. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  491. package/dist/shipping/deliverySelectionHandler.js +77 -0
  492. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  493. package/dist/shipping/deliverySelectionPort.js +1 -0
  494. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  495. package/dist/shipping/deliverySelectionSummary.js +53 -0
  496. package/dist/shipping/handlerSchemas.d.ts +21 -0
  497. package/dist/shipping/handlerSchemas.js +1 -0
  498. package/dist/shipping/index.d.ts +17 -0
  499. package/dist/shipping/index.js +8 -0
  500. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  501. package/dist/shipping/pickupPointSearchPort.js +1 -0
  502. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  503. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  504. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  505. package/dist/shipping/shippingRatePorts.js +1 -0
  506. package/package.json +72 -2
  507. package/release-gates.json +249 -8
  508. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  509. package/sql/20260807190000_comms_outbox_rail.sql +329 -0
  510. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  511. package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
  512. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  513. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  514. package/sql/20260813120000_communications_control_plane.sql +568 -0
  515. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  516. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  517. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  518. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  519. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  520. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  521. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  522. package/sql/20261019200000_communications_native_operations.sql +68 -0
  523. package/src/catalog/activeSkuSelection.ts +20 -0
  524. package/src/catalog/adminBindings.ts +59 -0
  525. package/src/catalog/adminBundleHandler.ts +165 -0
  526. package/src/catalog/adminBundleReadHandler.ts +261 -0
  527. package/src/catalog/adminBundleWritePort.ts +102 -0
  528. package/src/catalog/bundleAvailability.ts +129 -0
  529. package/src/catalog/bundleAvailabilityService.ts +171 -0
  530. package/src/catalog/bundleBindings.ts +29 -0
  531. package/src/catalog/bundleCatalogQueries.ts +98 -0
  532. package/src/catalog/bundleCatalogReadPort.ts +177 -0
  533. package/src/catalog/bundleCatalogStore.ts +269 -0
  534. package/src/catalog/bundleRuleCodes.ts +134 -0
  535. package/src/catalog/bundleRules.ts +153 -0
  536. package/src/catalog/catalogDraftStore.ts +71 -0
  537. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  538. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  539. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  540. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  541. package/src/catalog/contracts/bundleRead.ts +13 -0
  542. package/src/catalog/contracts/draft.ts +69 -0
  543. package/src/catalog/contracts/foundation.ts +104 -0
  544. package/src/catalog/contracts/index.ts +14 -0
  545. package/src/catalog/contracts/lifecycle.ts +74 -0
  546. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  547. package/src/catalog/contracts/productTypes.ts +35 -0
  548. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  549. package/src/catalog/contracts/sellable.ts +12 -0
  550. package/src/catalog/draftHandler.ts +105 -0
  551. package/src/catalog/draftPorts.ts +18 -0
  552. package/src/catalog/draftSchemas.ts +88 -0
  553. package/src/catalog/draftValidation.ts +100 -0
  554. package/src/catalog/foundationSchemas.ts +123 -0
  555. package/src/catalog/index.ts +43 -0
  556. package/src/catalog/mediaSourcePack.ts +219 -0
  557. package/src/catalog/productTypeSchemas.ts +70 -0
  558. package/src/catalog/publicationPorts.ts +44 -0
  559. package/src/catalog/readPorts.ts +61 -0
  560. package/src/catalog/referenceCatalogHandler.ts +39 -0
  561. package/src/catalog/resolvedSkuProjection.ts +76 -0
  562. package/src/catalog/sellableBundleHandler.ts +133 -0
  563. package/src/catalog/sellableSchemas.ts +33 -0
  564. package/src/catalog/sql.ts +14 -0
  565. package/src/catalog/validation.ts +3 -0
  566. package/src/communications/accountingDocumentDelivery.ts +63 -0
  567. package/src/communications/authEmailError.ts +10 -0
  568. package/src/communications/authEmailPorts.ts +2 -0
  569. package/src/communications/authSendEmailHook.ts +310 -0
  570. package/src/communications/capturedTransactionalDelivery.ts +29 -0
  571. package/src/communications/communicationSync.ts +96 -0
  572. package/src/communications/communicationSyncWorker.ts +302 -0
  573. package/src/communications/consentPolicy.ts +82 -0
  574. package/src/communications/consentPurposeRegistry.ts +31 -0
  575. package/src/communications/contracts/abandonedCart.ts +64 -0
  576. package/src/communications/contracts/authEmail.ts +73 -0
  577. package/src/communications/contracts/backInStock.ts +69 -0
  578. package/src/communications/contracts/blocks.ts +47 -0
  579. package/src/communications/contracts/checkoutExpired.ts +62 -0
  580. package/src/communications/contracts/checkoutRecovery.ts +68 -0
  581. package/src/communications/contracts/commerceEmailContent.ts +189 -0
  582. package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
  583. package/src/communications/contracts/consent.ts +13 -0
  584. package/src/communications/contracts/email.ts +210 -0
  585. package/src/communications/contracts/guideCard.ts +34 -0
  586. package/src/communications/contracts/index.ts +52 -0
  587. package/src/communications/contracts/inquiryContent.ts +53 -0
  588. package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
  589. package/src/communications/contracts/marketingAutomation.ts +45 -0
  590. package/src/communications/contracts/marketingSync.ts +109 -0
  591. package/src/communications/contracts/newsletter.ts +78 -0
  592. package/src/communications/contracts/newsletterWebhook.ts +23 -0
  593. package/src/communications/contracts/optOutContent.ts +23 -0
  594. package/src/communications/contracts/orderCanceled.ts +64 -0
  595. package/src/communications/contracts/orderDraft.ts +112 -0
  596. package/src/communications/contracts/orderPaid.ts +147 -0
  597. package/src/communications/contracts/orderRefunded.ts +63 -0
  598. package/src/communications/contracts/paymentFailed.ts +59 -0
  599. package/src/communications/contracts/publicIntake.ts +53 -0
  600. package/src/communications/contracts/render.ts +271 -0
  601. package/src/communications/contracts/reorderReminder.ts +59 -0
  602. package/src/communications/contracts/returnEmails.ts +113 -0
  603. package/src/communications/contracts/reviewEffects.ts +60 -0
  604. package/src/communications/contracts/reviewRequest.ts +59 -0
  605. package/src/communications/contracts/roleNotificationContent.ts +65 -0
  606. package/src/communications/contracts/shipmentDelivered.ts +59 -0
  607. package/src/communications/contracts/shipmentDispatched.ts +77 -0
  608. package/src/communications/contracts/shipmentException.ts +62 -0
  609. package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
  610. package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
  611. package/src/communications/contracts/subscriptionCancelled.ts +57 -0
  612. package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
  613. package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
  614. package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
  615. package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
  616. package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
  617. package/src/communications/contracts/subscriptionPaused.ts +58 -0
  618. package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
  619. package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
  620. package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
  621. package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
  622. package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
  623. package/src/communications/contracts/subscriptionResumed.ts +60 -0
  624. package/src/communications/contracts/subscriptionWelcome.ts +102 -0
  625. package/src/communications/contracts/subscriptionWinback.ts +62 -0
  626. package/src/communications/contracts/transactionalDelivery.ts +43 -0
  627. package/src/communications/contracts/types.ts +58 -0
  628. package/src/communications/contribution.ts +33 -0
  629. package/src/communications/deliveryReconciliation.ts +108 -0
  630. package/src/communications/emailEgress.ts +111 -0
  631. package/src/communications/emailHealthAssessment.ts +106 -0
  632. package/src/communications/emailHealthMetricsPort.ts +17 -0
  633. package/src/communications/emailNotificationControl.ts +28 -0
  634. package/src/communications/emailObservation.ts +43 -0
  635. package/src/communications/emailTransport.ts +167 -0
  636. package/src/communications/errors.ts +34 -0
  637. package/src/communications/index.ts +47 -0
  638. package/src/communications/integrationEventSignature.ts +49 -0
  639. package/src/communications/klaviyo/consentPush.ts +19 -0
  640. package/src/communications/klaviyo/index.ts +649 -0
  641. package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
  642. package/src/communications/nativeOperations.ts +40 -0
  643. package/src/communications/newsletterIntegrationContracts.ts +154 -0
  644. package/src/communications/newsletterWebhookPort.ts +104 -0
  645. package/src/communications/newsletterWebhookProcessing.ts +102 -0
  646. package/src/communications/originPolicy.ts +151 -0
  647. package/src/communications/postgresCommunicationSync.ts +127 -0
  648. package/src/communications/postgresConsentEvaluator.ts +49 -0
  649. package/src/communications/postgresDeliveryReconciliation.ts +101 -0
  650. package/src/communications/postgresNativeOperations.ts +55 -0
  651. package/src/communications/postgresNotificationRecipients.ts +57 -0
  652. package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
  653. package/src/communications/preferencesToken.ts +62 -0
  654. package/src/communications/publicPreferenceToken.ts +68 -0
  655. package/src/communications/resend/index.ts +350 -0
  656. package/src/communications/shipmentNotifications.ts +78 -0
  657. package/src/communications/sql.ts +7 -0
  658. package/src/communications/standardWebhooks.ts +84 -0
  659. package/src/communications/status.ts +51 -0
  660. package/src/communications/transactionalDelivery.ts +114 -0
  661. package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
  662. package/src/communications/unsubscribeToken.ts +186 -0
  663. package/src/communications/validation.ts +3 -0
  664. package/src/composition/catalogContribution.ts +32 -0
  665. package/src/composition/index.ts +1 -0
  666. package/src/http/index.ts +1 -0
  667. package/src/http/webhookSignature.ts +96 -0
  668. package/src/inventory/adminInventory.ts +251 -0
  669. package/src/inventory/contracts/forecast.ts +96 -0
  670. package/src/inventory/contracts/index.ts +18 -0
  671. package/src/inventory/contracts/inventory.ts +14 -0
  672. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  673. package/src/inventory/contracts/reservation.ts +20 -0
  674. package/src/inventory/forecast.ts +239 -0
  675. package/src/inventory/index.ts +21 -0
  676. package/src/inventory/inventoryHandlers.ts +215 -0
  677. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  678. package/src/inventory/ports.ts +46 -0
  679. package/src/inventory/renewalReservation.ts +88 -0
  680. package/src/inventory/reservationPolicy.ts +27 -0
  681. package/src/inventory/schemas.ts +128 -0
  682. package/src/inventory/sql.ts +5 -0
  683. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  684. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  685. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  686. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  687. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  688. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  689. package/src/pricing/acquisitionReadiness.ts +148 -0
  690. package/src/pricing/acquisitionTerms.ts +125 -0
  691. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  692. package/src/pricing/catalogFactsProvenance.ts +102 -0
  693. package/src/pricing/catalogFactsSchemas.ts +69 -0
  694. package/src/pricing/commerceOfferPricing.ts +224 -0
  695. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  696. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  697. package/src/pricing/commercePriceAuthority.ts +104 -0
  698. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  699. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  700. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  701. package/src/pricing/commerceQuoteHandler.ts +184 -0
  702. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  703. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  704. package/src/pricing/contracts/authority.ts +44 -0
  705. package/src/pricing/contracts/evidence.ts +25 -0
  706. package/src/pricing/contracts/index.ts +17 -0
  707. package/src/pricing/contracts/money.ts +32 -0
  708. package/src/pricing/contracts/offer.ts +42 -0
  709. package/src/pricing/contracts/offerPolicy.ts +17 -0
  710. package/src/pricing/contracts/policy.ts +26 -0
  711. package/src/pricing/contracts/provenance.ts +19 -0
  712. package/src/pricing/contracts/quote.ts +106 -0
  713. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  714. package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
  715. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  716. package/src/pricing/index.ts +52 -0
  717. package/src/pricing/offerPolicyReadiness.ts +110 -0
  718. package/src/pricing/offerPolicySchemas.ts +23 -0
  719. package/src/pricing/offerPricingSchemas.ts +92 -0
  720. package/src/pricing/ports.ts +17 -0
  721. package/src/pricing/pricingPolicyToken.ts +119 -0
  722. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  723. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  724. package/src/pricing/quotePorts.ts +58 -0
  725. package/src/pricing/quotePrimitives.ts +21 -0
  726. package/src/pricing/quotePromotionEvidence.ts +82 -0
  727. package/src/pricing/quoteSchemas.ts +230 -0
  728. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  729. package/src/pricing/taxProfileGuard.ts +33 -0
  730. package/src/pricing/validation.ts +3 -0
  731. package/src/promotions/adminBindings.ts +53 -0
  732. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  733. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  734. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  735. package/src/promotions/adminPromotionsContracts.ts +121 -0
  736. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  737. package/src/promotions/adminPromotionsHandler.ts +72 -0
  738. package/src/promotions/contracts/index.ts +387 -0
  739. package/src/promotions/index.ts +22 -0
  740. package/src/promotions/promoDataPort.ts +65 -0
  741. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  742. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  743. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  744. package/src/promotions/promotionClaims.ts +70 -0
  745. package/src/promotions/promotionCodePreview.ts +183 -0
  746. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  747. package/src/promotions/promotionQuoteAcceptance.ts +284 -0
  748. package/src/promotions/promotionQuoteBinding.ts +158 -0
  749. package/src/promotions/quoteAutomaticPromotions.ts +58 -0
  750. package/src/promotions/quotePromoDiscounts.ts +185 -0
  751. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  752. package/src/promotions/quotePromotionV2.ts +223 -0
  753. package/src/promotions/sql.ts +5 -0
  754. package/src/promotions/validation.ts +3 -0
  755. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  756. package/src/shipping/addressCanonical.ts +31 -0
  757. package/src/shipping/addressPorts.ts +48 -0
  758. package/src/shipping/addressSchemas.ts +28 -0
  759. package/src/shipping/adminShippingRateHandler.ts +72 -0
  760. package/src/shipping/contracts/address.ts +71 -0
  761. package/src/shipping/contracts/delivery.ts +114 -0
  762. package/src/shipping/contracts/index.ts +18 -0
  763. package/src/shipping/contracts/shippingRate.ts +7 -0
  764. package/src/shipping/customerAddressesHandler.ts +100 -0
  765. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  766. package/src/shipping/deliverySelectionHandler.ts +94 -0
  767. package/src/shipping/deliverySelectionPort.ts +10 -0
  768. package/src/shipping/deliverySelectionSummary.ts +56 -0
  769. package/src/shipping/handlerSchemas.ts +19 -0
  770. package/src/shipping/index.ts +23 -0
  771. package/src/shipping/pickupPointSearchPort.ts +8 -0
  772. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  773. package/src/shipping/shippingRatePorts.ts +36 -0
@@ -0,0 +1,127 @@
1
+ import type { SqlExecutor } from "@openlup/core/readiness";
2
+ import type { PostgresDataGatewayEnv } from "../data/index.js";
3
+ import type { CommunicationSyncStore } from "./communicationSyncWorker.js";
4
+ import type { NewsletterSyncEvent, NewsletterSyncEventType, NewsletterMarketingPurpose } from "./contracts/newsletter.js";
5
+ import type { NativeCommunicationsOptions } from "./nativeOperations.js";
6
+ import { nativeEmail, nativeFingerprint, nativeInteger, nativeJson, nativeStamp, nativeText, nativeTransactions } from "./postgresNativeOperations.js";
7
+
8
+ /** Structural seam for the existing outbox store, scoped to this operation's transaction. */
9
+ export interface NativeSyncRail {
10
+ claimBatch(input: { eventTypes: string[]; knownEventTypes: string[]; batchSize: number; visibilitySeconds: number; maxAttempts: number }): Promise<{ id: string; attempts: number; metadata: Record<string, unknown> }[]>;
11
+ markProcessed(input: { eventId: string; claimToken: string; metadata?: Record<string, unknown> }): Promise<{ applied: boolean }>;
12
+ markFailed(input: { eventId: string; claimToken: string; error: string; outcome: "retry" | "discard"; baseDelaySeconds: number; maxDelaySeconds: number; maxAttempts: number; snoozeSeconds: number }): Promise<{ status: string }>;
13
+ }
14
+ export interface NativeSyncRegistration {
15
+ sourceKey: string; contactId: string; normalizedEmail: string; eventType: NewsletterSyncEventType;
16
+ purpose: NewsletterMarketingPurpose; payload?: Record<string, unknown>;
17
+ originProviderKind?: string | null; originProviderEventId?: string | null;
18
+ remoteProfileId?: string | null; remoteListId?: string | null;
19
+ }
20
+ export interface NativeSyncOptions extends NativeCommunicationsOptions {
21
+ providerKind: string;
22
+ rail: (db: SqlExecutor) => NativeSyncRail;
23
+ }
24
+ export interface NativeCommunicationSyncStore extends CommunicationSyncStore {
25
+ registerWork(input: NativeSyncRegistration): Promise<{ id: string }>;
26
+ registerPreference(input: {email:string;purpose:"tester_program"|NewsletterMarketingPurpose;state:"denied"|"suppressed"}): Promise<{updated:true;email:string;purpose:"tester_program"|NewsletterMarketingPurpose;state:"denied"|"suppressed";workId:string|null;alreadySuppressed:boolean}>;
27
+ close(): Promise<void>;
28
+ }
29
+ export const nativeSyncEventType = (binding: string): string => `communication.native.sync.${nativeFingerprint(nativeText(binding))}`;
30
+ function nativeSyncRegistration(options: Pick<NativeSyncOptions,"binding"|"providerKind">, input: NativeSyncRegistration): (db: SqlExecutor) => Promise<{id:string}> {
31
+ const binding=nativeText(options.binding),provider=nativeText(options.providerKind),eventType=nativeSyncEventType(binding);
32
+ const email = nativeEmail(input.normalizedEmail), source = nativeText(input.sourceKey), contact = nativeText(input.contactId);
33
+ if (!["subscribe","update","unsubscribe","suppress"].includes(input.eventType) || !["marketing_launch_offer","marketing_newsletter"].includes(input.purpose)) throw new Error("native_sync_event_invalid");
34
+ const payload = nativeJson(input.payload ?? {});
35
+ const refs = [input.originProviderKind ?? null,input.originProviderEventId ?? null,input.remoteProfileId ?? null,input.remoteListId ?? null].map(value => value === null ? null : nativeText(value));
36
+ const fact = nativeFingerprint(JSON.stringify([provider,contact,email,input.eventType,input.purpose,payload,...refs]));
37
+ // Reuse the rail's ordering barrier for successive preferences of one contact/target.
38
+ const aggregate = nativeFingerprint(JSON.stringify([binding,provider,contact])).slice(0,32);
39
+ return async db => {
40
+ // One rail identity for each source event and exact target binding.
41
+ await db.query(`INSERT INTO public.outbox_events (aggregate_type,aggregate_id,event_type,idempotency_key,payload)
42
+ VALUES ('communication_native_sync',$4::uuid,$1,$2,$3::jsonb) ON CONFLICT (event_type,idempotency_key) DO NOTHING`, [eventType,source,payload,aggregate]);
43
+ const work = (await db.query("SELECT id FROM public.outbox_events WHERE event_type=$1 AND idempotency_key=$2 FOR UPDATE", [eventType,source])).rows[0]!;
44
+ await db.query(`INSERT INTO public.communication_native_sync_state
45
+ (work_id,binding,provider_kind,contact_id,normalized_email,purpose,event_kind,origin_provider_kind,origin_provider_event_id,remote_profile_id,remote_list_id,command_fingerprint)
46
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (work_id) DO NOTHING`, [work.id,binding,provider,contact,email,input.purpose,input.eventType,...refs,fact]);
47
+ const state = (await db.query("SELECT command_fingerprint FROM public.communication_native_sync_state WHERE work_id=$1", [work.id])).rows[0];
48
+ if (state?.command_fingerprint !== fact) throw new Error("native_sync_registration_conflict");
49
+ return { id: nativeText(work.id) };
50
+ };
51
+ }
52
+ /** Trusted host transaction seam; registration validation remains shared with the standalone store. */
53
+ export async function registerNativeSyncWorkInTransaction(db: SqlExecutor, options: Pick<NativeSyncOptions,"binding"|"providerKind">, input: NativeSyncRegistration): Promise<{id:string}> {
54
+ return nativeSyncRegistration(options,input)(db);
55
+ }
56
+
57
+ /** No sidecar claim/lease/drainer. Only runCommunicationSyncDispatch executes membership effects. */
58
+ export function createPostgresCommunicationSyncStore(env: PostgresDataGatewayEnv, options: NativeSyncOptions): NativeCommunicationSyncStore {
59
+ const binding = nativeText(options.binding), provider = nativeText(options.providerKind), eventType = nativeSyncEventType(binding);
60
+ const lane = nativeTransactions(env, options, ["public.communication_native_sync_state","public.outbox_events","public.communication_recipient_consents"]);
61
+ const checkProvider = (value: string) => { if (value !== provider) throw new Error("native_sync_binding_conflict"); };
62
+ return {
63
+ close: lane.close,
64
+ async registerWork(input) {
65
+ const register=nativeSyncRegistration(options,input);
66
+ return lane.run(db => register(db as SqlExecutor));
67
+ },
68
+ registerPreference(input) {
69
+ const email=nativeEmail(input.email),fingerprint=nativeFingerprint(email),purpose=nativeText(input.purpose);
70
+ if (!["tester_program","marketing_launch_offer","marketing_newsletter"].includes(purpose) || !["denied","suppressed"].includes(input.state)) throw new Error("native_preference_invalid");
71
+ return lane.run(async db=>{
72
+ await db.query("SELECT pg_advisory_xact_lock(hashtextextended($1,0))",[JSON.stringify([fingerprint,purpose])]);
73
+ const old=(await db.query("SELECT state,updated_at FROM public.communication_recipient_consents WHERE recipient_fingerprint=$1 AND purpose=$2 FOR UPDATE",[fingerprint,purpose])).rows[0];
74
+ const row=old?.state===input.state?old:(await db.query(`INSERT INTO public.communication_recipient_consents (recipient_fingerprint,purpose,state)
75
+ VALUES ($1,$2,$3) ON CONFLICT (recipient_fingerprint,purpose) DO UPDATE SET state=excluded.state,updated_at=clock_timestamp() RETURNING state,updated_at`,[fingerprint,purpose,input.state])).rows[0]!;
76
+ let workId:string|null=null;
77
+ if(purpose!=="tester_program"){
78
+ const sourceKey=nativeFingerprint(JSON.stringify([fingerprint,purpose,input.state,nativeStamp(row.updated_at)]));
79
+ workId=(await registerNativeSyncWorkInTransaction(db as SqlExecutor,options,{sourceKey,contactId:fingerprint,normalizedEmail:email,eventType:input.state==="suppressed"?"suppress":"unsubscribe",purpose:purpose as NewsletterMarketingPurpose})).id;
80
+ }
81
+ return {updated:true as const,email,purpose:purpose as "tester_program"|NewsletterMarketingPurpose,state:input.state,workId,alreadySuppressed:old?.state===input.state};
82
+ });
83
+ },
84
+ claimBatch(input) {
85
+ checkProvider(input.providerKind);
86
+ nativeInteger(input.batchSize,1,100); nativeInteger(input.visibilitySeconds,60,3600); nativeInteger(input.maxAttempts,1,20);
87
+ return lane.run(async db => {
88
+ const work = await options.rail(db as SqlExecutor).claimBatch({ eventTypes: [eventType], knownEventTypes: [eventType], batchSize: input.batchSize,visibilitySeconds: input.visibilitySeconds,maxAttempts: input.maxAttempts });
89
+ const events: NewsletterSyncEvent[] = [];
90
+ for (const row of work) {
91
+ const state = (await db.query("UPDATE public.communication_native_sync_state SET max_attempts=$4 WHERE work_id=$1 AND binding=$2 AND provider_kind=$3 RETURNING *", [row.id,binding,provider,input.maxAttempts])).rows[0];
92
+ if (!state) throw new Error("native_sync_sidecar_missing");
93
+ const payload = (await db.query("SELECT payload FROM public.outbox_events WHERE id=$1", [row.id])).rows[0]!.payload;
94
+ nativeJson(payload);
95
+ events.push({ id: nativeText(row.id), claimToken: nativeText(row.metadata.claimToken), contactId: nativeText(state.contact_id), normalizedEmail: nativeEmail(state.normalized_email), eventType: state.event_kind as NewsletterSyncEventType,
96
+ purpose: state.purpose as NewsletterMarketingPurpose,payload: payload as Record<string,unknown>, attemptCount: nativeInteger(row.attempts-1,0,19),
97
+ originProviderKind: state.origin_provider_kind as string | null,originProviderEventId: state.origin_provider_event_id as string | null,remoteProfileId: state.remote_profile_id as string | null,remoteListId: state.remote_list_id as string | null });
98
+ }
99
+ return events;
100
+ });
101
+ },
102
+ markSent(input) {
103
+ checkProvider(input.providerKind);
104
+ const summary = nativeJson(input.responseSummary ?? {}), remote = input.remoteProfileId == null ? null : nativeText(input.remoteProfileId);
105
+ return lane.run(async db => {
106
+ const row = (await db.query(`SELECT e.id FROM public.outbox_events e JOIN public.communication_native_sync_state s ON s.work_id=e.id
107
+ WHERE e.id=$1 AND s.binding=$2 AND s.provider_kind=$3 AND e.event_type=$4 AND e.status='processing' AND e.metadata->>'claimToken'=$5 FOR UPDATE OF e,s`, [input.eventId,binding,provider,eventType,input.claimToken])).rows[0];
108
+ if (!row) return false;
109
+ if (!(await options.rail(db as SqlExecutor).markProcessed({ eventId: input.eventId,claimToken: input.claimToken })).applied) throw new Error("native_sync_ack_lost");
110
+ await db.query("UPDATE public.communication_native_sync_state SET remote_profile_id=$2,response_summary=$3::jsonb WHERE work_id=$1", [input.eventId,remote,summary]);
111
+ return true;
112
+ });
113
+ },
114
+ markFailed(input) {
115
+ checkProvider(input.providerKind); nativeInteger(input.backoffSeconds,0,86400);
116
+ return lane.run(async db => {
117
+ const row = (await db.query(`SELECT s.max_attempts FROM public.outbox_events e JOIN public.communication_native_sync_state s ON s.work_id=e.id
118
+ WHERE e.id=$1 AND s.binding=$2 AND s.provider_kind=$3 AND e.event_type=$4 AND e.status='processing' AND e.metadata->>'claimToken'=$5 FOR UPDATE OF e,s`, [input.eventId,binding,provider,eventType,input.claimToken])).rows[0];
119
+ if (!row) return false;
120
+ const result = await options.rail(db as SqlExecutor).markFailed({ eventId: input.eventId,claimToken: input.claimToken,error: nativeText(input.error,2000),outcome: input.retry ? "retry" : "discard",baseDelaySeconds: input.backoffSeconds,maxDelaySeconds: input.backoffSeconds,maxAttempts: nativeInteger(Number(row.max_attempts),1,20),snoozeSeconds: 300 });
121
+ if (!["failed","discarded"].includes(result.status)) throw new Error("native_sync_ack_lost");
122
+ await db.query("UPDATE public.communication_native_sync_state SET response_summary=$2::jsonb WHERE work_id=$1", [input.eventId,JSON.stringify({ status: result.status })]);
123
+ return true;
124
+ });
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,49 @@
1
+ import type { PostgresDataGatewayEnv, PostgresDataGatewayOptions } from "../data/index.js";
2
+ import { evaluateCommunicationPolicy } from "./consentPolicy.js";
3
+ import { defaultCommunicationPurposeRegistry } from "./consentPurposeRegistry.js";
4
+ import type { CommunicationPurposeRegistry } from "./contracts/consent.js";
5
+ import type { CommunicationPermissionState } from "./contracts/types.js";
6
+ import { NativeConsentEvaluationUnavailableError, type NativeConsentDecision, type NativeConsentInput } from "./nativeOperations.js";
7
+ import { nativeEmail, nativeFingerprint, nativeText, nativeTransactions } from "./postgresNativeOperations.js";
8
+ export interface NativeConsentOptions extends PostgresDataGatewayOptions {
9
+ registry?: CommunicationPurposeRegistry<string>;
10
+ /** Host-owned authoritative tester pause, read in the decision transaction. Missing means unpaused. */
11
+ testerPaused?: (db: import("../data/index.js").PgQueryExecutor, input: NativeConsentInput) => Promise<boolean>;
12
+ }
13
+ export interface NativeConsentEvaluator {
14
+ evaluate(input: NativeConsentInput): Promise<NativeConsentDecision>;
15
+ close(): Promise<void>;
16
+ readonly guarantees: { readonly managedFrequencyCaps: false; readonly legacyContactTouching: false; readonly sendTimeFence: false };
17
+ }
18
+ /** Bounded P4 policy, authoritative native permission and durable decision. Evaluation never sends. */
19
+ export function createPostgresConsentEvaluator(env: PostgresDataGatewayEnv, options: NativeConsentOptions = {}): NativeConsentEvaluator {
20
+ const lane = nativeTransactions(env, options, ["public.communication_native_policy_decisions","public.communication_recipient_consents","public.notification_recipients"]);
21
+ const registry = options.registry ?? defaultCommunicationPurposeRegistry;
22
+ return {
23
+ close: lane.close,
24
+ guarantees: { managedFrequencyCaps: false, legacyContactTouching: false, sendTimeFence: false },
25
+ async evaluate(input) {
26
+ try {
27
+ const email = nativeEmail(input.email), fingerprint = nativeFingerprint(email), purpose = nativeText(input.purpose);
28
+ const recipientKind = input.recipientKind ?? "external_contact";
29
+ if (!["external_contact","tester","customer","lead","admin_internal"].includes(recipientKind)) throw new Error("native_recipient_kind_invalid");
30
+ return await lane.run(async db => {
31
+ const permission = (await db.query("SELECT state,captured_at,updated_at FROM public.communication_recipient_consents WHERE recipient_fingerprint=$1 AND purpose=$2 FOR SHARE", [fingerprint,purpose])).rows[0];
32
+ if (permission && !["granted","denied","suppressed"].includes(String(permission.state))) throw new Error("native_permission_invalid");
33
+ const paused = purpose === "tester_program" && options.testerPaused ? await options.testerPaused(db,input) : false;
34
+ if (typeof paused !== "boolean") throw new Error("native_pause_invalid");
35
+ let result = evaluateCommunicationPolicy({ purpose,recipientKind,permission: { state: permission ? permission.state as CommunicationPermissionState : null,legacySequencePaused: paused } }, registry);
36
+ let activeRecipient: boolean | null = null;
37
+ if (purpose === "admin_notification") {
38
+ const rows = (await db.query("SELECT active FROM public.notification_recipients WHERE id=$1 AND email=$2 FOR SHARE", [input.sourceTable === "notification_recipients" ? input.sourceId ?? null : null,email])).rows;
39
+ activeRecipient = rows.length === 1 && rows[0].active === true;
40
+ if (!activeRecipient) result = { decision: "blocked",reason: "notification_recipient_inactive" };
41
+ }
42
+ const row = (await db.query(`INSERT INTO public.communication_native_policy_decisions (recipient_fingerprint,purpose,allowed,reason,permission_snapshot,source_table,source_id)
43
+ VALUES ($1,$2,$3,$4,$5::jsonb,$6,$7) RETURNING id`, [fingerprint,purpose,result.decision === "allowed",result.reason,JSON.stringify({ permission: permission ?? null,legacySequencePaused: paused,activeRecipient,recipientKind }),input.sourceTable ?? null,input.sourceId ?? null])).rows[0];
44
+ return { allowed: result.decision === "allowed",reason: result.reason,decisionId: nativeText(row?.id) };
45
+ });
46
+ } catch (error) { throw new NativeConsentEvaluationUnavailableError(error); }
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,101 @@
1
+ import type { PostgresDataGatewayEnv } from "../data/index.js";
2
+ import type { NativeCommunicationsOptions, NativeDeliveryStore, NativeObservationKind } from "./nativeOperations.js";
3
+ import { nativeEmail, nativeFingerprint, nativeInteger, nativeStamp, nativeText, nativeTime, nativeTransactions } from "./postgresNativeOperations.js";
4
+ const kinds: NativeObservationKind[] = ["sent", "delayed", "delivered", "failed", "bounced", "complained"];
5
+ // Terminal/suppression precedence is monotonic; event chronology remains in the fact log.
6
+ const rank = (kind: NativeObservationKind | null) => kind === null ? -1 : kinds.indexOf(kind);
7
+ export function createPostgresDeliveryReconciliationPort(env: PostgresDataGatewayEnv, options: NativeCommunicationsOptions): NativeDeliveryStore {
8
+ const binding = nativeText(options.binding);
9
+ const lane = nativeTransactions(env, options, ["public.communication_native_sends", "public.communication_native_observations", "public.communication_recipient_consents"]);
10
+ const store: NativeDeliveryStore = {
11
+ close: lane.close,
12
+ async registerNetworkTarget(input) {
13
+ if (input.evidence !== "network_accepted" || input.messageId.startsWith("captured:")) throw new Error("native_network_acceptance_required");
14
+ const values = [binding, nativeText(input.sourceKey), nativeText(input.messageId), nativeFingerprint(nativeEmail(input.recipientEmail)), nativeText(input.templateReference), nativeTime(input.acceptedAt)];
15
+ return lane.run(async db => {
16
+ await db.query(`INSERT INTO public.communication_native_sends (binding,source_key,message_id,recipient_fingerprint,template_reference,accepted_at)
17
+ VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (binding,source_key) DO NOTHING`, values);
18
+ const row = (await db.query(`SELECT * FROM public.communication_native_sends WHERE binding=$1 AND source_key=$2 FOR UPDATE`, values.slice(0,2))).rows[0];
19
+ if (!row || row.message_id !== values[2] || row.recipient_fingerprint !== values[3] || row.template_reference !== values[4] || nativeStamp(row.accepted_at) !== values[5]) throw new Error("native_send_registration_conflict");
20
+ return { id: nativeText(row.id) };
21
+ });
22
+ },
23
+ findFrozenSends(input) {
24
+ const now = nativeTime(input.now), limit = nativeInteger(input.limit,1,100), grace = nativeInteger(input.graceMinutes,0,10080);
25
+ return lane.run(async db => (await db.query(`SELECT id,message_id FROM public.communication_native_sends
26
+ WHERE binding=$1 AND abandoned_at IS NULL AND (observed_state IS NULL OR observed_state IN ('sent','delayed'))
27
+ AND accepted_at <= $2::timestamptz - make_interval(mins => $3) ORDER BY last_poll_at NULLS FIRST, accepted_at,id LIMIT $4`, [binding,now,grace,limit])).rows.map(row => ({ id: nativeText(row.id), messageId: nativeText(row.message_id) })));
28
+ },
29
+ async applyObservation(input) {
30
+ const kind = input.kind;
31
+ if (!kinds.includes(kind) || !["poll","callback"].includes(input.source)) throw new Error("native_observation_invalid");
32
+ const at = nativeTime(input.occurredAt), key = nativeText(input.observationKey), message = nativeText(input.messageId);
33
+ const fingerprint = input.recipientEmail === null ? null : nativeFingerprint(nativeEmail(input.recipientEmail));
34
+ await lane.run(async db => {
35
+ const send = (await db.query("SELECT * FROM public.communication_native_sends WHERE id=$1 AND binding=$2 FOR UPDATE", [input.sendId,binding])).rows[0];
36
+ if (!send || send.message_id !== message || (fingerprint !== null && send.recipient_fingerprint !== fingerprint)) throw new Error("native_observation_identity_conflict");
37
+ const fact = nativeFingerprint(JSON.stringify([input.sendId,message,kind,at,send.recipient_fingerprint]));
38
+ const inserted = await db.query(`INSERT INTO public.communication_native_observations (binding,observation_key,send_id,kind,occurred_at,source,fact_fingerprint)
39
+ VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (binding,observation_key) DO NOTHING RETURNING observation_key`, [binding,key,input.sendId,kind,at,input.source,fact]);
40
+ if (!inserted.rows.length) {
41
+ const old = (await db.query("SELECT fact_fingerprint FROM public.communication_native_observations WHERE binding=$1 AND observation_key=$2", [binding,key])).rows[0];
42
+ if (old?.fact_fingerprint !== fact) throw new Error("native_observation_replay_conflict");
43
+ return;
44
+ }
45
+ if (rank(kind) > rank(send.observed_state as NativeObservationKind | null) || (kind === send.observed_state && at > nativeStamp(send.observed_at))) {
46
+ await db.query("UPDATE public.communication_native_sends SET observed_state=$3,observed_at=$4 WHERE id=$1 AND binding=$2", [input.sendId,binding,kind,at]);
47
+ }
48
+ if (kind === "bounced" || kind === "complained") {
49
+ for (const purpose of ["marketing_launch_offer","marketing_newsletter","tester_program"]) await db.query(`INSERT INTO public.communication_recipient_consents (recipient_fingerprint,purpose,state,captured_at,updated_at)
50
+ VALUES ($1,$2,'suppressed',$3,$3) ON CONFLICT (recipient_fingerprint,purpose) DO UPDATE SET state='suppressed',updated_at=greatest(communication_recipient_consents.updated_at,excluded.updated_at)`, [send.recipient_fingerprint,purpose,at]);
51
+ }
52
+ });
53
+ },
54
+ applyPolledEvent(input) {
55
+ return store.applyObservation({ sendId: input.sendId, messageId: input.messageId, observationKey: nativeFingerprint(JSON.stringify([input.sendId,input.eventType,nativeTime(input.eventAt)])), kind: input.eventType as NativeObservationKind, occurredAt: input.eventAt, recipientEmail: input.recipientEmail, source: "poll" });
56
+ },
57
+ async recordPollAttempt(input) {
58
+ await lane.run(async db => {
59
+ const result = await db.query("UPDATE public.communication_native_sends SET last_poll_at=greatest(last_poll_at,$3::timestamptz),not_found_count=0 WHERE id=$1 AND binding=$2 RETURNING id", [input.sendId,binding,nativeTime(input.attemptedAt)]);
60
+ if (!result.rows.length) throw new Error("native_send_not_found");
61
+ });
62
+ },
63
+ recordSendNotFound(input) {
64
+ nativeInteger(input.threshold,1,100);
65
+ return lane.run(async db => {
66
+ const send = (await db.query("SELECT abandoned_at FROM public.communication_native_sends WHERE id=$1 AND binding=$2 FOR UPDATE", [input.sendId,binding])).rows[0];
67
+ if (!send) throw new Error("native_send_not_found");
68
+ const result = (await db.query(`UPDATE public.communication_native_sends SET not_found_count=least(not_found_count,2147483646)+1,last_poll_at=clock_timestamp(),
69
+ abandoned_at=CASE WHEN not_found_count >= $3-1 THEN coalesce(abandoned_at,clock_timestamp()) ELSE abandoned_at END WHERE id=$1 AND binding=$2 RETURNING abandoned_at`, [input.sendId,binding,input.threshold])).rows[0]!;
70
+ return { abandoned: send.abandoned_at === null && result.abandoned_at !== null };
71
+ });
72
+ },
73
+ findByMessageId(messageId) {
74
+ const message = nativeText(messageId);
75
+ return lane.run(async db => {
76
+ const rows = (await db.query("SELECT id FROM public.communication_native_sends WHERE binding=$1 AND message_id=$2", [binding,message])).rows;
77
+ if (rows.length > 1) throw new Error("native_message_identity_ambiguous");
78
+ return rows.length ? {id:nativeText(rows[0].id)} : null;
79
+ });
80
+ },
81
+ async listEvidence(input) {
82
+ const limit = nativeInteger(input.limit,1,100);
83
+ const ids = await lane.run(async db => (await db.query("SELECT id FROM public.communication_native_sends WHERE binding=$1 ORDER BY accepted_at DESC,id DESC LIMIT $2", [binding,limit])).rows.map(row=>nativeText(row.id)));
84
+ const evidence = [];
85
+ for (const id of ids) { const row = await store.readEvidence(id); if (row) evidence.push(row); }
86
+ return evidence;
87
+ },
88
+ readEvidence(id) {
89
+ return lane.run(async db => {
90
+ const row = (await db.query("SELECT * FROM public.communication_native_sends WHERE id=$1 AND binding=$2", [id,binding])).rows[0];
91
+ if (!row) return null;
92
+ const observations = (await db.query("SELECT observation_key,kind,occurred_at,source FROM public.communication_native_observations WHERE send_id=$1 AND binding=$2 ORDER BY occurred_at,observation_key", [id,binding])).rows;
93
+ return { id: nativeText(row.id), sourceKey: nativeText(row.source_key), messageId: nativeText(row.message_id), acceptedAt: nativeStamp(row.accepted_at),
94
+ observedState: row.observed_state as NativeObservationKind | null, observedAt: row.observed_at === null ? null : nativeStamp(row.observed_at),
95
+ lastPollAt: row.last_poll_at === null ? null : nativeStamp(row.last_poll_at), notFoundCount: nativeInteger(Number(row.not_found_count),0,2147483647), abandonedAt: row.abandoned_at === null ? null : nativeStamp(row.abandoned_at),
96
+ observations: observations.map(fact => ({ key: nativeText(fact.observation_key), kind: fact.kind as NativeObservationKind, occurredAt: nativeStamp(fact.occurred_at), source: fact.source as "poll" | "callback" })) };
97
+ });
98
+ },
99
+ };
100
+ return store;
101
+ }
@@ -0,0 +1,55 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createPostgresRoleFreeTransactionLane, type PgQueryExecutor, type PostgresDataGatewayEnv, type PostgresDataGatewayOptions } from "../data/index.js";
3
+
4
+ export const nativeFingerprint = (value: string): string => createHash("sha256").update(value).digest("hex");
5
+ export function nativeText(value: unknown, max = 256): string {
6
+ if (typeof value !== "string" || !value.trim() || value.length > max || [...value].some(character => character.codePointAt(0)! < 32 || character.codePointAt(0) === 127)) throw new Error("native_communications_text_invalid");
7
+ return value;
8
+ }
9
+ export function nativeEmail(value: unknown): string {
10
+ const email = nativeText(value, 320).trim().toLowerCase();
11
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) throw new Error("native_communications_email_invalid");
12
+ return email;
13
+ }
14
+ export function nativeTime(value: unknown): string {
15
+ const parts = typeof value === "string" ? /^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.(\d{1,6}))?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.exec(value) : null;
16
+ if (!parts || typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new Error("native_communications_time_invalid");
17
+ if (new Date(value.slice(0,10) + "T00:00:00Z").toISOString().slice(0,10) !== value.slice(0,10)) throw new Error("native_communications_time_invalid");
18
+ // PostgreSQL stores microseconds. Never collapse two distinct immutable facts to JS milliseconds.
19
+ return new Date(value).toISOString().replace(/\.\d{3}Z$/, `.${(parts[1] ?? "").padEnd(6,"0")}Z`);
20
+ }
21
+ export function nativeInteger(value: number, min: number, max: number): number {
22
+ if (!Number.isInteger(value) || value < min || value > max) throw new Error("native_communications_bound_invalid");
23
+ return value;
24
+ }
25
+ export function nativeJson(value: unknown): string {
26
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("native_communications_object_invalid");
27
+ const text = JSON.stringify(value);
28
+ if (text.length > 4096) throw new Error("native_communications_object_too_large");
29
+ return text;
30
+ }
31
+ export const nativeStamp = (value: unknown): string => nativeTime(value instanceof Date ? value.toISOString() : value);
32
+ /** Private capability-local helper. The host owns the connection authority; no SET ROLE or runtime DDL. */
33
+ export function nativeTransactions(env: PostgresDataGatewayEnv, options: PostgresDataGatewayOptions, tables: string[]) {
34
+ if (!env.connectionString.trim()) throw new Error("communications_database_url_required");
35
+ const lane = createPostgresRoleFreeTransactionLane(env, options);
36
+ return {
37
+ close: lane.close,
38
+ run: <T>(work: (db: PgQueryExecutor) => Promise<T>): Promise<T> => lane.run(async gateway => {
39
+ const db = gateway as PgQueryExecutor;
40
+ // Effective privileges include PUBLIC, role membership and column/default grants.
41
+ // Missing relations, malformed responses and untrusted execution roles all refuse.
42
+ const row = (await db.query(`SELECT current_user IN ('anon','authenticated','authenticator') AS browser,
43
+ EXISTS (SELECT 1 FROM unnest($1::text[]) t(name), pg_roles r
44
+ WHERE r.rolname IN ('anon','authenticated','authenticator') AND
45
+ (has_table_privilege(r.oid, t.name, 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER')
46
+ OR has_any_column_privilege(r.oid, t.name, 'SELECT,INSERT,UPDATE,REFERENCES')))
47
+ OR EXISTS (SELECT 1 FROM unnest($1::text[]) t(name), pg_class c,
48
+ LATERAL aclexplode(coalesce(c.relacl, acldefault('r',c.relowner))) a WHERE c.oid = t.name::regclass AND a.grantee = 0)
49
+ OR EXISTS (SELECT 1 FROM unnest($1::text[]) t(name), pg_attribute c,
50
+ LATERAL aclexplode(c.attacl) a WHERE c.attrelid = t.name::regclass AND a.grantee = 0) AS unsafe`, [tables])).rows[0];
51
+ if (row?.browser !== false || row?.unsafe !== false) throw new Error("native_communications_privileges_unsafe");
52
+ return work(db);
53
+ }),
54
+ };
55
+ }
@@ -0,0 +1,57 @@
1
+ import { CommunicationConflictError } from "./errors.js";
2
+ import type { PgQueryExecutor, PostgresDataGatewayEnv, PostgresDataGatewayOptions } from "../data/index.js";
3
+ import type { NativeNotificationRecipient, NativeNotificationType } from "./nativeOperations.js";
4
+ import { nativeEmail, nativeStamp, nativeText, nativeTransactions } from "./postgresNativeOperations.js";
5
+ const types: NativeNotificationType[] = ["packaging_digest","new_signup","commerce_payment_critical"];
6
+ function notificationType(value: NativeNotificationType): NativeNotificationType {
7
+ if (!types.includes(value)) throw new Error("native_notification_type_invalid");
8
+ return value;
9
+ }
10
+ export interface NativeNotificationRecipientsOptions extends PostgresDataGatewayOptions { /** Verified host operator principal, never request body data. */ operatorId: string }
11
+ export interface NativeNotificationRecipientsPort {
12
+ listNotificationRecipients(input: { notification_type: NativeNotificationType }): Promise<{ recipients: NativeNotificationRecipient[] }>;
13
+ createNotificationRecipient(input: { email: string; name: string | null; notification_type: NativeNotificationType; active: true }): Promise<{ created: true; email: string; notification_type: NativeNotificationType }>;
14
+ updateNotificationRecipient(input: { recipientId: string; active: boolean }): Promise<{ updated: true; recipientId: string }>;
15
+ deleteNotificationRecipient(input: { recipientId: string }): Promise<{ deleted: true; recipientId: string }>;
16
+ close(): Promise<void>;
17
+ }
18
+ export function createPostgresNotificationRecipientsPort(env: PostgresDataGatewayEnv, options: NativeNotificationRecipientsOptions): NativeNotificationRecipientsPort {
19
+ const operator = nativeText(options.operatorId);
20
+ const lane = nativeTransactions(env, options, ["public.notification_recipients","public.platform_communication_operators","public.platform_human_memberships","public.platform_control_operators"]);
21
+ const run = <T>(work: (db: PgQueryExecutor) => Promise<T>) => lane.run(async db => {
22
+ // Match the reference communications control-plane admission, retaining row locks through commit.
23
+ const allowed = (await db.query(`SELECT o.principal_id FROM public.platform_communication_operators o
24
+ JOIN public.platform_human_memberships m ON m.principal_id=o.principal_id
25
+ WHERE o.principal_id=$1 AND o.active AND m.membership_state='active' AND m.role IN ('admin','distributor')
26
+ AND NOT EXISTS (SELECT 1 FROM public.platform_control_operators c WHERE c.principal_id=o.principal_id) FOR SHARE OF o,m`, [operator])).rows;
27
+ if (allowed.length !== 1) throw new Error("communications_operator_required");
28
+ return work(db);
29
+ });
30
+ return {
31
+ close: lane.close,
32
+ listNotificationRecipients(input) {
33
+ const type = notificationType(input.notification_type);
34
+ return run(async db => ({ recipients: (await db.query("SELECT id,email,name,active,notification_type,created_at FROM public.notification_recipients WHERE notification_type=$1 ORDER BY created_at,id", [type])).rows.map(row => {
35
+ if (typeof row.active !== "boolean" || (row.name !== null && typeof row.name !== "string")) throw new Error("native_notification_row_invalid");
36
+ return { id: nativeText(row.id),email: nativeEmail(row.email),name: row.name as string | null,active: row.active,notification_type: notificationType(row.notification_type as NativeNotificationType),created_at: nativeStamp(row.created_at) };
37
+ }) }));
38
+ },
39
+ async createNotificationRecipient(input) {
40
+ const email = nativeEmail(input.email), type = notificationType(input.notification_type);
41
+ if (input.active !== true || (input.name !== null && typeof input.name !== "string")) throw new Error("native_notification_request_invalid");
42
+ const name = input.name === null ? null : input.name.trim();
43
+ if (name !== null && name.length > 256) throw new Error("native_notification_request_invalid");
44
+ try { return await run(async db => {
45
+ await db.query("INSERT INTO public.notification_recipients (email,name,notification_type,active) VALUES ($1,$2,$3,true)", [email,name,type]);
46
+ return { created: true as const,email,notification_type: type };
47
+ }); } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "23505") throw new CommunicationConflictError("notification_recipient_conflict"); throw error; }
48
+ },
49
+ async updateNotificationRecipient(input) {
50
+ if (typeof input.active !== "boolean" || Object.keys(input).some(key => !["active","recipientId"].includes(key))) throw new Error("native_notification_request_invalid");
51
+ return run(async db => { await db.query("UPDATE public.notification_recipients SET active=$2,updated_at=clock_timestamp() WHERE id=$1", [input.recipientId,input.active]); return { updated: true as const,recipientId: input.recipientId }; });
52
+ },
53
+ deleteNotificationRecipient(input) {
54
+ return run(async db => { await db.query("DELETE FROM public.notification_recipients WHERE id=$1", [input.recipientId]); return { deleted: true as const,recipientId: input.recipientId }; });
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,79 @@
1
+ import {
2
+ createPostgresRoleFreeTransactionLane,
3
+ type PostgresDataGatewayEnv,
4
+ type PostgresDataGatewayOptions,
5
+ } from "../data/index.js";
6
+ import type {
7
+ TransactionalDeliveryReceipt,
8
+ TransactionalDeliveryReceiptStore,
9
+ } from "./contracts/transactionalDelivery.js";
10
+
11
+ interface PostgresTransactionalDeliveryStoreClient {
12
+ rpc(name: string, args: Record<string, unknown>): PromiseLike<{
13
+ data: unknown;
14
+ error: { code?: string; message?: string } | null;
15
+ }>;
16
+ }
17
+
18
+ export interface PostgresTransactionalDeliveryStore extends TransactionalDeliveryReceiptStore {
19
+ close(): Promise<void>;
20
+ }
21
+
22
+ function failure(name: string, error: { code?: string; message?: string }): Error & { code?: string } {
23
+ const result = new Error(`${name}_failed: ${error.message ?? error.code ?? "unknown"}`) as Error & { code?: string };
24
+ result.code = error.code;
25
+ return result;
26
+ }
27
+
28
+ function receipt(data: unknown): TransactionalDeliveryReceipt {
29
+ const value = (Array.isArray(data) ? data[0] : data) as Record<string, unknown> | undefined;
30
+ if (!value || typeof value !== "object") throw new Error("transactional_delivery_receipt_invalid");
31
+ const common = {
32
+ idempotencyKey: String(value.idempotency_key ?? ""),
33
+ commandFingerprint: String(value.command_fingerprint ?? ""),
34
+ attemptCount: Number(value.attempt_count ?? 0),
35
+ };
36
+ if (value.state === "accepted" && typeof value.delivery_reference === "string" && value.error_code == null) {
37
+ return { ...common, state: "accepted", deliveryReference: value.delivery_reference, errorCode: null };
38
+ }
39
+ if (value.state === "failed" && value.delivery_reference == null && typeof value.error_code === "string") {
40
+ return { ...common, state: "failed", deliveryReference: null, errorCode: value.error_code };
41
+ }
42
+ throw new Error("transactional_delivery_receipt_invalid");
43
+ }
44
+
45
+ /** Direct-Postgres receipt adapter: one role-free transaction for each store operation. */
46
+ export function createPostgresTransactionalDeliveryStore(
47
+ env: PostgresDataGatewayEnv,
48
+ options: PostgresDataGatewayOptions = {},
49
+ createTransactionLane: typeof createPostgresRoleFreeTransactionLane = createPostgresRoleFreeTransactionLane,
50
+ ): PostgresTransactionalDeliveryStore {
51
+ const transactions = createTransactionLane(env, options);
52
+ const call = <T>(work: (client: PostgresTransactionalDeliveryStoreClient) => Promise<T>): Promise<T> =>
53
+ transactions.run((client) => work(client as PostgresTransactionalDeliveryStoreClient));
54
+ const write = (name: string, args: Record<string, unknown>) => call(async (client) => {
55
+ const { data, error } = await client.rpc(name, args);
56
+ if (error) throw failure(name, error);
57
+ return receipt(data);
58
+ });
59
+ return {
60
+ async readReceipt(idempotencyKey) {
61
+ return call(async (client) => {
62
+ const { data, error } = await client.rpc("transactional_delivery_read_receipt", { p_idempotency_key: idempotencyKey });
63
+ if (error) throw failure("transactional_delivery_read_receipt", error);
64
+ return Array.isArray(data) && data.length === 0 ? null : receipt(data);
65
+ });
66
+ },
67
+ recordAccepted: (input) => write("transactional_delivery_record_accepted", {
68
+ p_idempotency_key: input.idempotencyKey,
69
+ p_command_fingerprint: input.commandFingerprint,
70
+ p_delivery_reference: input.deliveryReference,
71
+ }),
72
+ recordFailed: (input) => write("transactional_delivery_record_failed", {
73
+ p_idempotency_key: input.idempotencyKey,
74
+ p_command_fingerprint: input.commandFingerprint,
75
+ p_error_code: input.errorCode,
76
+ }),
77
+ close: () => transactions.close(),
78
+ };
79
+ }
@@ -0,0 +1,62 @@
1
+ import type { CommunicationPurposeRegistry } from "./contracts/consent.js";
2
+ import { createHmac, timingSafeEqual } from "node:crypto";
3
+
4
+ export type LegacyCommunicationPreferencePurpose = "tester_program" | "marketing_launch_offer" | "marketing_newsletter";
5
+
6
+ export interface CommunicationPreferenceTokenPayload<Purpose extends string = LegacyCommunicationPreferencePurpose> {
7
+ email: string;
8
+ purpose: Purpose;
9
+ expiresAt: number;
10
+ }
11
+
12
+ export function verifyCommunicationPreferenceToken(token: string, secret: string, nowMs?: number): CommunicationPreferenceTokenPayload | null;
13
+ export function verifyCommunicationPreferenceToken<Purpose extends string>(token: string, secret: string, nowMs: number | undefined, registry: CommunicationPurposeRegistry<Purpose>): CommunicationPreferenceTokenPayload<Purpose> | null;
14
+ export function verifyCommunicationPreferenceToken(
15
+ token: string,
16
+ secret: string,
17
+ nowMs = Date.now(),
18
+ registry?: CommunicationPurposeRegistry<string>,
19
+ ): CommunicationPreferenceTokenPayload<string> | null {
20
+ const parts = token.split(".");
21
+ if (parts.length !== 2 || !secret) return null;
22
+ const [encoded, signature] = parts;
23
+ const expected = hmac(encoded, secret);
24
+ if (!safeEqual(signature, expected)) return null;
25
+
26
+ try {
27
+ const payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as Record<string, unknown>;
28
+ const email = typeof payload.email === "string" ? payload.email.trim().toLowerCase() : "";
29
+ const purpose = payload.purpose;
30
+ const expiresAt = typeof payload.expiresAt === "number" ? payload.expiresAt : 0;
31
+ if (!email.includes("@") || expiresAt < nowMs) return null;
32
+ if (
33
+ typeof purpose !== "string" || (registry ? !registry.has(purpose) :
34
+ purpose !== "tester_program" &&
35
+ purpose !== "marketing_launch_offer" &&
36
+ purpose !== "marketing_newsletter")
37
+ ) {
38
+ return null;
39
+ }
40
+ return { email, purpose, expiresAt };
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ export function createCommunicationPreferenceToken<Purpose extends string = LegacyCommunicationPreferencePurpose>(
47
+ payload: CommunicationPreferenceTokenPayload<Purpose>,
48
+ secret: string,
49
+ ): string {
50
+ const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
51
+ return `${encoded}.${hmac(encoded, secret)}`;
52
+ }
53
+
54
+ function hmac(value: string, secret: string): string {
55
+ return createHmac("sha256", secret).update(value).digest("hex");
56
+ }
57
+
58
+ function safeEqual(left: string, right: string): boolean {
59
+ const leftBuffer = Buffer.from(left);
60
+ const rightBuffer = Buffer.from(right);
61
+ return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
62
+ }
@@ -0,0 +1,68 @@
1
+ import type { CommunicationPurposeRegistry, CommunicationPurposeDefinition } from "./contracts/consent.js";
2
+ import type { CommunicationPreferenceTokenPayload } from "./preferencesToken.js";
3
+
4
+ export interface PublicPreferenceTokenSupabaseClient {
5
+ rpc(fn: string, args: Record<string, unknown>): Promise<{ data: unknown; error?: unknown }>;
6
+ }
7
+
8
+ export type PublicPreferenceState = "denied" | "suppressed";
9
+
10
+ export function createSupabasePublicPreferenceTokenPort(
11
+ client: PublicPreferenceTokenSupabaseClient,
12
+ ) {
13
+ return {
14
+ async updatePreference(payload: CommunicationPreferenceTokenPayload, state: PublicPreferenceState) {
15
+ const contact = await client.rpc("communication_touch_contact", {
16
+ p_email: payload.email,
17
+ p_first_name: null,
18
+ p_last_name: null,
19
+ p_status: "active",
20
+ p_metadata: { source: "public_communication_preferences" },
21
+ });
22
+ if (contact.error || typeof contact.data !== "string") {
23
+ throw new Error("Communication contact write failed");
24
+ }
25
+
26
+ const event = await client.rpc("communication_record_permission_event", {
27
+ p_contact_id: contact.data,
28
+ p_purpose: payload.purpose,
29
+ p_state: state,
30
+ p_source: "public_communication_preferences",
31
+ p_source_ref: { route: "/api/bff/communications/preferences", purpose: payload.purpose },
32
+ p_reason: "user_preference_update",
33
+ p_metadata: {
34
+ actorType: "contact_self_service",
35
+ captureMethod: "preference_token",
36
+ },
37
+ p_captured_at: new Date().toISOString(),
38
+ p_create_sync_event: payload.purpose !== "tester_program",
39
+ });
40
+ if (event.error) throw new Error("Communication preference write failed");
41
+
42
+ return {
43
+ updated: true,
44
+ email: payload.email,
45
+ // Keep the legacy adapter declaration expanded when the token default names a type alias.
46
+ purpose: payload.purpose as "marketing_launch_offer" | "marketing_newsletter" | "tester_program",
47
+ state,
48
+ };
49
+ },
50
+ };
51
+ }
52
+
53
+ /** The host owns persistence and any synchronization policy for registered purposes. */
54
+ export function createRegisteredPublicPreferenceTokenPort<Purpose extends string>(input: {
55
+ registry: CommunicationPurposeRegistry<Purpose>;
56
+ persist: (payload: CommunicationPreferenceTokenPayload<Purpose>, state: PublicPreferenceState,
57
+ definition: CommunicationPurposeDefinition<Purpose>) => Promise<unknown>;
58
+ }) {
59
+ const { registry, persist } = input;
60
+ return {
61
+ async updatePreference(payload: CommunicationPreferenceTokenPayload<Purpose>, state: PublicPreferenceState) {
62
+ if (!registry.has(payload.purpose)) throw new Error("unregistered_communication_purpose");
63
+ if (state !== "denied" && state !== "suppressed") throw new Error("communication_preference_state_invalid");
64
+ const definition = registry.purposes.find(item => item.id === payload.purpose)!;
65
+ return persist(payload, state, definition);
66
+ },
67
+ };
68
+ }