@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,649 @@
1
+ import type { ConsentPushPort, ConsentPushRequest, ConsentPushResult, ConsentReadback, MarketingSyncProvider, NegativeFeedback, ProfileProjection, SyncAudience, SyncBinding, SyncRequest, SyncResult } from "../contracts/marketingSync.js";
2
+ import type { MarketingAutomationClaim, MarketingAutomationObservation, MarketingAutomationProviderPort, MarketingAutomationScope } from "../contracts/marketingAutomation.js";
3
+ import type { NewsletterSyncProviderPort } from "../contracts/newsletter.js";
4
+ import { consentCommand } from "./consentPush.js";
5
+
6
+ // Retain the cause when a grant admission CAS throws before any journal bytes
7
+ // survive. Shared host availability keeps it across adapter reconstruction;
8
+ // a fresh host process still starts quarantined for explicit reconciliation.
9
+ type QuarantineCause = { kind: "grants"; grants: Map<string, string> } | { kind: "non-grant" };
10
+ const quarantineCauses = new WeakMap<object, QuarantineCause>();
11
+ const providerCooling = new WeakMap<object, number>();
12
+ const REVISION = "2026-07-15";
13
+ const ORIGIN = "https://a.klaviyo.com";
14
+ type Request = SyncRequest | ConsentPushRequest;
15
+ type Result = SyncResult | ConsentPushResult;
16
+ type Operation = Request["operation"];
17
+ type Failure = Extract<Result, { kind: "refused" | "retryable" | "unknown" }>;
18
+ type Reason = Extract<Failure, { kind: "refused" }>["reason"];
19
+ type Subject = {
20
+ binding: SyncBinding; subjectReference: string; generation: number; erased: boolean;
21
+ id?: string; email: string; externalReference: string;
22
+ ownedFields: readonly (keyof ProfileProjection["fields"])[]; ownedProperties: readonly string[];
23
+ };
24
+ type LocalEvidence = {
25
+ binding: SyncBinding; subjectReference: string; audience: SyncAudience; purpose: string;
26
+ action: "profile" | "grant" | "withdraw" | "erase"; current: boolean;
27
+ projection?: ProfileProjection; caseReference?: string; authorityReference?: string; allPurposes?: boolean;
28
+ };
29
+ type Authorization = { binding: SyncBinding; subjectReference: string; audience: SyncAudience;
30
+ operation: Operation; expiresAtMs: number };
31
+ type Verification = {
32
+ binding: SyncBinding; audience: SyncAudience; evidenceReference: string; revision: string;
33
+ operations: readonly Exclude<Operation, "erasure.read">[]; scopes: readonly string[];
34
+ noTrigger: { profile: boolean; accountWideGrant: boolean; doi: boolean };
35
+ safeCreate: boolean; safeListRemoval: boolean; suppressionSafeAtEffect: boolean;
36
+ negativeSourcesComplete: boolean; listMappings: Readonly<Record<string, SyncAudience>>;
37
+ };
38
+ type Entry = { request: Request; semantic: string; target: Subject; result?: Result;
39
+ phase: "admitted" | "dispatched" | "settled"; remoteId?: string; grantSettled?: boolean };
40
+ type Ledger = { version: 1; coolingUntil: number; nextAt: number; slot: boolean; quarantined: boolean;
41
+ entries: Record<string, Entry>; mappings: Record<string, string>; tombstones: string[];
42
+ withdrawals: Record<string, { owner: string; audience: SyncAudience; earlier: string[]; resolved: boolean }> };
43
+ type Storage = {
44
+ /** Shared plain mutable record for this account/environment, independent of CAS failure.
45
+ * Start quarantined after host restart; only explicit host reconciliation clears it.
46
+ * Every adapter namespace/instance uses the same record. Never reset it at construction. */
47
+ availability: { account: string; environment: string; busy: boolean; quarantined: boolean };
48
+ /** Shared across every adapter for the account. false proves no commit; throws may have committed. */
49
+ get(key: string): Promise<string | null>;
50
+ compareAndSwap(key: string, expected: string | null, value: string): Promise<boolean>;
51
+ };
52
+ type Configuration = {
53
+ binding: SyncBinding; audience: SyncAudience;
54
+ /** Synchronous current host registry lookup; never parses an opaque subject reference. */
55
+ subjectMapper(subjectReference: string): Subject | undefined;
56
+ resolveCredential(): { binding: SyncBinding; key: string } | undefined;
57
+ authorizations: ReadonlyMap<string, Authorization>; localEvidence: ReadonlyMap<string, LocalEvidence>;
58
+ storage: Storage; verification?: Verification;
59
+ /** Private authenticated settlement facts from the host's existing reconciliation journal. */
60
+ grantSettlements?: ReadonlyMap<string, { binding: SyncBinding; subjectReference: string; requestId: string;
61
+ source: "provider-read"; evidenceReference: string; settledAtMs: number }>;
62
+ fetch?: typeof fetch; now?: () => number;
63
+ maxRequestBytes?: number; maxResponseBytes?: number; maxFeedbackItems?: number;
64
+ timeoutMs?: number; rateIntervalMs?: number;
65
+ legacy?: { scope: MarketingAutomationScope; claims: ReadonlyMap<string, { claim: MarketingAutomationClaim; request: Request }> };
66
+ };
67
+ const object = (value: unknown): Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
68
+ const text = (value: unknown): value is string => typeof value === "string" && value.trim().length > 0 && value.length <= 512 && ![...value].some(c => c.charCodeAt(0) < 32);
69
+ const id = (value: unknown): value is string => typeof value === "string" && /^[A-Za-z0-9_-]{1,200}$/.test(value);
70
+ const positive = (value: number) => Number.isSafeInteger(value) && value > 0;
71
+ const bytes = (value: unknown) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
72
+ function canonical(value: unknown): string {
73
+ if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
74
+ if (value && typeof value === "object") return "{" + Object.keys(value).filter(k => (value as Record<string, unknown>)[k] !== undefined).sort().map(k => JSON.stringify(k) + ":" + canonical((value as Record<string, unknown>)[k])).join(",") + "}";
75
+ return JSON.stringify(value);
76
+ }
77
+ const equal = (a: unknown, b: unknown) => canonical(a) === canonical(b);
78
+ function freeze<T>(value: T): T {
79
+ if (value && typeof value === "object") { for (const child of Object.values(value)) freeze(child); Object.freeze(value); }
80
+ return value;
81
+ }
82
+ const copy = <T>(value: T): T => freeze(structuredClone(value));
83
+ const exact = (value: unknown, keys: string[]) => Object.keys(object(value)).sort().join() === [...keys].sort().join();
84
+ const validBinding = (b: unknown) => exact(b, ["account", "environment", "namespace", "mappingVersion"]) && Object.values(object(b)).every(text);
85
+ const validAudience = (a: unknown) => object(a).kind === "account-wide" ? exact(a, ["kind"]) : object(a).kind === "audience" && exact(a, ["kind", "reference"]) && text(object(a).reference);
86
+ const overlaps = (a: SyncAudience, b: SyncAudience) => a.kind === "account-wide" || b.kind === "account-wide" || equal(a, b);
87
+ const keyOf = (r: Request) => canonical([r.binding, r.operation, r.subjectReference, r.requestId]);
88
+ const subjectKey = (r: Request) => canonical([r.binding, r.subjectReference]);
89
+ const semantic = (r: Request) => canonical([r.audience, r.payload]);
90
+ const echo = (r: Pick<Request, "binding" | "operation" | "subjectReference" | "requestId" | "generation">) => ({ binding: r.binding, operation: r.operation, subjectReference: r.subjectReference, requestId: r.requestId, generation: r.generation });
91
+ function result(r: Pick<Request, "binding" | "operation" | "subjectReference" | "requestId" | "generation">, value: object): Result { return copy({ ...value, ...echo(r) }) as Result; }
92
+ const refused = (r: Request, reason: Reason) => result(r, { kind: "refused", reason });
93
+ const retry = (r: Request, reason: "cooling" | "host" | "provider", afterSeconds = 1) => result(r, { kind: "retryable", reason, afterSeconds });
94
+ const unknown = (r: Request, reason: "dispatch_unresolved" | "acceptance_ambiguous" | "record_failed", receipt?: unknown) => result(r, { kind: "unknown", reason, ...(receipt ? { receipt } : {}) });
95
+ function validRequest(r: Request, max: number): boolean {
96
+ if (!exact(r, ["binding", "audience", "subjectReference", "requestId", "generation", "deadlineMs", "authorizationReference", "operation", "payload"]) ||
97
+ !validBinding(r.binding) || !validAudience(r.audience) || !text(r.subjectReference) || !text(r.requestId) || !text(r.authorizationReference) ||
98
+ !Number.isSafeInteger(r.generation) || r.generation < 0 || !Number.isFinite(r.deadlineMs) || bytes(r) > max) return false;
99
+ const p = r.payload;
100
+ switch (r.operation) {
101
+ case "profile.sync": {
102
+ const v = p as SyncRequest<"profile.sync">["payload"];
103
+ if (!exact(p, ["projection", "consentEvidenceReference"]) || !text(v.consentEvidenceReference) || !exact(v.projection, ["email", "externalReference", "fields", "properties"])) return false;
104
+ const q = v.projection;
105
+ return text(q.email) && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(q.email) && text(q.externalReference) && q.fields !== null && !Array.isArray(q.fields) && typeof q.fields === "object" &&
106
+ Object.entries(q.fields).every(([k, value]) => ["givenName", "familyName", "locale"].includes(k) && text(value)) &&
107
+ q.properties !== null && typeof q.properties === "object" && !Array.isArray(q.properties) && Object.entries(q.properties).every(([k, value]) => text(k) &&
108
+ (typeof value === "string" && value.length <= 512 || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)));
109
+ }
110
+ case "consent.read": return exact(p, []);
111
+ case "erasure.request": return exact(p, ["caseReference", "authorityReference"]) && Object.values(p).every(text);
112
+ case "erasure.read": return exact(p, ["receipt"]) && object(object(p).receipt).operation === "erasure.request";
113
+ case "consent.push": return exact(p, ["action", "consentEvidenceReference"]) && text(object(p).consentEvidenceReference) &&
114
+ (r.payload.action === "grant" && r.audience.kind === "audience" || r.payload.action === "withdraw");
115
+ default: return false;
116
+ }
117
+ }
118
+ function validEntry(entry: Entry): boolean {
119
+ if (!validRequest(entry.request, Number.MAX_SAFE_INTEGER) || entry.semantic !== semantic(entry.request) ||
120
+ !["admitted", "dispatched", "settled"].includes(entry.phase) || !equal(entry.target.binding, entry.request.binding) ||
121
+ entry.target.subjectReference !== entry.request.subjectReference) return false;
122
+ if (!entry.result) return entry.phase !== "settled";
123
+ const value = entry.result;
124
+ if (!equal(echo(value), echo(entry.request))) return false;
125
+ const receipt = "receipt" in value ? value.receipt : undefined;
126
+ if (receipt && (!["profile.sync", "erasure.request", "consent.push"].includes(entry.request.operation) || !equal(receipt.request, entry.request) || receipt.operation !== entry.request.operation)) return false;
127
+ switch (value.kind) {
128
+ case "refused": return ["invalid_request", "binding", "provenance", "stale", "deadline", "unsupported", "verification_pending", "restricted", "identity_conflict", "provider_refusal"].includes(value.reason);
129
+ case "retryable": return ["cooling", "host", "provider"].includes(value.reason) && Number.isFinite(value.afterSeconds) && value.afterSeconds > 0;
130
+ case "unknown": return ["dispatch_unresolved", "acceptance_ambiguous", "record_failed"].includes(value.reason);
131
+ case "accepted": case "acknowledged": return !!receipt && ["profile.sync", "erasure.request", "consent.push"].includes(entry.request.operation) &&
132
+ ["confirmed", "unknown"].includes(value.journal) && (entry.request.operation !== "consent.push" || "reconcileRequired" in value && typeof value.reconcileRequired === "boolean");
133
+ case "completed": {
134
+ if (value.operation !== "consent.read" || !["confirmed", "unknown"].includes(value.journal)) return false;
135
+ const v = value.value;
136
+ return equal(v.audience, entry.request.audience) && Number.isFinite(v.observedAtMs) && ["complete", "partial", "unsupported"].includes(v.coverage) &&
137
+ ["subscribed", "unsubscribed", "pending", "never_subscribed", "unknown"].includes(v.remoteConsent) && Array.isArray(v.feedback) && v.feedback.every(f =>
138
+ validAudience(f.audience) && text(f.sourceReference) && (f.occurredAtMs === null || Number.isFinite(f.occurredAtMs)) &&
139
+ (["hard_bounce", "invalid_address"].includes(f.kind) ? f.outcome === "deny_delivery" : ["unsubscribe", "complaint", "manual_block"].includes(f.kind) && f.outcome === "deny_permission"));
140
+ }
141
+ default: return false;
142
+ }
143
+ }
144
+ function emptyLedger(): Ledger { return { version: 1, coolingUntil: 0, nextAt: 0, slot: false, quarantined: false, entries: {}, mappings: {}, tombstones: [], withdrawals: {} }; }
145
+ function parseLedger(raw: string | null): Ledger {
146
+ if (raw === null) return emptyLedger();
147
+ const v = JSON.parse(raw) as Ledger;
148
+ if (v.version !== 1 || !Number.isFinite(v.coolingUntil) || !Number.isFinite(v.nextAt) || typeof v.quarantined !== "boolean" ||
149
+ !v.entries || Array.isArray(v.entries) || !v.mappings || !Array.isArray(v.tombstones) || !v.withdrawals || typeof v.slot !== "boolean" || !Object.values(v.entries).every(validEntry)) throw new Error("journal");
150
+ return v;
151
+ }
152
+ function profileAttributes(p: ProfileProjection, creating: boolean) {
153
+ const names = { givenName: "first_name", familyName: "last_name", locale: "locale" };
154
+ return { ...(creating ? { email: p.email, external_id: p.externalReference } : {}),
155
+ ...Object.fromEntries(Object.entries(p.fields).map(([k, v]) => [names[k as keyof typeof names], v])), properties: p.properties };
156
+ }
157
+ function profileData(body: unknown, target: Subject, projection?: ProfileProjection): string | null {
158
+ const d = object(object(body).data), a = object(d.attributes);
159
+ if (d.type !== "profile" || !id(d.id) || (target.id && d.id !== target.id) || a.email !== target.email || a.external_id !== target.externalReference) return null;
160
+ if (projection) {
161
+ const wanted = profileAttributes(projection, false);
162
+ for (const [k, v] of Object.entries(wanted)) {
163
+ if (k === "properties") { for (const [name, value] of Object.entries(object(v))) if (!equal(object(a.properties)[name], value)) return null; }
164
+ else if (!equal(a[k], v)) return null;
165
+ }
166
+ }
167
+ return d.id;
168
+ }
169
+ function consentValue(body: unknown, target: Subject, audience: SyncAudience, verification: Verification, now: number, max: number): ConsentReadback | null {
170
+ if (!profileData(body, target)) return null;
171
+ const m = object(object(object(object(object(object(body).data).attributes).subscriptions).email).marketing);
172
+ const states: Record<string, ConsentReadback["remoteConsent"]> = { SUBSCRIBED: "subscribed", UNSUBSCRIBED: "unsubscribed", NEVER_SUBSCRIBED: "never_subscribed", PENDING: "pending" };
173
+ let complete = verification.negativeSourcesComplete && Array.isArray(m.suppression) && Array.isArray(m.list_suppressions) && typeof m.can_receive_email_marketing === "boolean" && typeof m.consent === "string" && m.consent in states;
174
+ const feedback: NegativeFeedback[] = [];
175
+ const kinds = { UNSUBSCRIBE: "unsubscribe", SPAM_REPORT: "complaint", SPAM_COMPLAINT: "complaint", USER_SUPPRESSED: "manual_block", HARD_BOUNCE: "hard_bounce", INVALID_EMAIL: "invalid_address" } as const;
176
+ function add(reason: keyof typeof kinds, timestamp: unknown, scope: SyncAudience, source: string) {
177
+ if (feedback.length >= max) { complete = false; return; }
178
+ const kind = kinds[reason];
179
+ feedback.push(copy({ kind, outcome: kind === "hard_bounce" || kind === "invalid_address" ? "deny_delivery" : "deny_permission",
180
+ sourceReference: source, occurredAtMs: typeof timestamp === "string" && Number.isFinite(Date.parse(timestamp)) ? Date.parse(timestamp) : null, audience: scope }) as NegativeFeedback);
181
+ }
182
+ if (m.consent === "UNSUBSCRIBED") add("UNSUBSCRIBE", m.consent_timestamp, { kind: "account-wide" }, `negative:${target.id}:consent`);
183
+ for (const [source, values] of [["global", m.suppression], ["list", m.list_suppressions]] as const) {
184
+ if (!Array.isArray(values)) continue;
185
+ // Current-state arrays are bounded by the response rail; no history, metrics discovery or pagination.
186
+ for (const [n, value] of values.entries()) {
187
+ const item = object(value);
188
+ if (typeof item.reason !== "string" || !Object.hasOwn(kinds, item.reason)) { complete = false; continue; }
189
+ const scope = source === "global" ? { kind: "account-wide" } as const : typeof item.list_id === "string" ? verification.listMappings[item.list_id] : undefined;
190
+ if (!scope || !validAudience(scope)) { complete = false; continue; }
191
+ add(item.reason as keyof typeof kinds, item.timestamp, scope, `negative:${target.id}:${source}:${n}`);
192
+ }
193
+ }
194
+ if (m.can_receive_email_marketing === false && feedback.length === 0) complete = false;
195
+ return copy({ audience, observedAtMs: now, remoteConsent: audience.kind === "account-wide" ? states[String(m.consent)] ?? "unknown" : "unknown",
196
+ feedback, coverage: complete ? "complete" : "partial" });
197
+ }
198
+
199
+ /** Opt-in Node adapter; owner TEST evidence is required before any bound operation is enabled. */
200
+ export function createKlaviyoMarketingAdapter(input: Configuration): MarketingSyncProvider & MarketingAutomationProviderPort & { readonly consentPush: ConsentPushPort; readonly newsletter: NewsletterSyncProviderPort } {
201
+ const binding = copy(input.binding), audience = copy(input.audience), now = input.now ?? Date.now;
202
+ const maxRequestBytes = input.maxRequestBytes ?? 65536, maxResponseBytes = input.maxResponseBytes ?? 1048576;
203
+ const maxFeedbackItems = input.maxFeedbackItems ?? 64, timeoutMs = input.timeoutMs ?? 10000, rateIntervalMs = input.rateIntervalMs ?? 100;
204
+ if (!validBinding(binding) || !validAudience(audience) || ![maxRequestBytes, maxResponseBytes, maxFeedbackItems, timeoutMs, rateIntervalMs].every(positive)) throw new Error("Invalid adapter configuration");
205
+ const verification = input.verification ? copy(input.verification) : undefined;
206
+ if (verification && (!equal(verification.binding, binding) || !equal(verification.audience, audience) || verification.revision !== REVISION || !text(verification.evidenceReference))) throw new Error("Invalid verification binding");
207
+ const operationScopes: Record<Exclude<Operation, "erasure.read">, string[]> = {
208
+ "profile.sync": ["profiles:read", "profiles:write"], "consent.read": ["profiles:read"], "erasure.request": ["data-privacy:write"],
209
+ "consent.push": ["profiles:read", "subscriptions:write", "lists:write"],
210
+ };
211
+ const capability = (operation: Exclude<Operation, "erasure.read">) => verification?.operations.includes(operation) && operationScopes[operation].every(s => verification.scopes.includes(s))
212
+ ? { state: "supported" as const, evidenceReference: verification.evidenceReference, maxRequestBytes, maxFeedbackItems }
213
+ : { state: "verification_pending" as const };
214
+ const capabilities: ConsentPushPort["capabilities"] = copy({ binding, audience, operations: {
215
+ "profile.sync": capability("profile.sync"), "consent.read": capability("consent.read"),
216
+ "erasure.request": audience.kind === "account-wide" ? capability("erasure.request") : { state: "unsupported" },
217
+ "erasure.read": { state: "unsupported" }, "consent.push": { ...capability("consent.push"), grantEffect: "account-wide" },
218
+ } });
219
+ const accountKey = canonical(["communications-adapter", binding.account, binding.environment]);
220
+ const availability = input.storage.availability;
221
+ if (!availability || availability.account !== binding.account || availability.environment !== binding.environment ||
222
+ !["busy", "quarantined"].every(k => { const d = Object.getOwnPropertyDescriptor(availability, k); return d?.writable && typeof d.value === "boolean"; })) throw new Error("Invalid account availability record");
223
+ if (!availability.quarantined) quarantineCauses.delete(availability);
224
+ let quarantine = false;
225
+ const closeAccount = (grantCause?: Request) => {
226
+ const previous = quarantineCauses.get(availability);
227
+ if (!grantCause || previous?.kind === "non-grant" ||
228
+ (quarantine || availability.quarantined) && !previous) quarantineCauses.set(availability, { kind: "non-grant" });
229
+ else {
230
+ const grants = previous?.kind === "grants" ? previous.grants : new Map<string, string>();
231
+ grants.set(keyOf(grantCause), subjectKey(grantCause));
232
+ quarantineCauses.set(availability, { kind: "grants", grants });
233
+ }
234
+ quarantine = true; availability.quarantined = true;
235
+ };
236
+ const isWithdrawal = (r: Request) => r.operation === "consent.push" && r.payload.action === "withdraw";
237
+ function availabilityQuarantined(r: Request): boolean {
238
+ // Exempt only unresolved grant fences; every non-grant admission predicate remains.
239
+ return (quarantine || availability.quarantined) &&
240
+ !(isWithdrawal(r) && quarantineCauses.get(availability)?.kind === "grants");
241
+ }
242
+ const load = async () => {
243
+ let raw: string | null;
244
+ try { raw = await input.storage.get(accountKey); } catch { closeAccount(); throw new Error("storage"); }
245
+ try {
246
+ const ledger = parseLedger(raw);
247
+ ledger.coolingUntil = Math.max(ledger.coolingUntil, providerCooling.get(availability) ?? 0);
248
+ return { raw, ledger };
249
+ } catch { closeAccount(); throw new Error("journal"); }
250
+ };
251
+ async function quarantineAccount(affectedKey?: string, grantCause?: Request): Promise<void> {
252
+ closeAccount(grantCause);
253
+ // An error reply may follow a committed CAS. Write the existing quarantine bit
254
+ // against the actual latest bytes, rather than trusting the failed writer's copy.
255
+ try {
256
+ const raw = await input.storage.get(accountKey), ledger = parseLedger(raw);
257
+ ledger.quarantined ||= !grantCause;
258
+ const saved = affectedKey ? ledger.entries[affectedKey]?.result : undefined;
259
+ if (affectedKey && saved && (saved.kind === "accepted" || saved.kind === "acknowledged" || saved.kind === "completed")) ledger.entries[affectedKey].result = result(ledger.entries[affectedKey].request, { ...saved, journal: "unknown" });
260
+ await input.storage.compareAndSwap(accountKey, raw, canonical(ledger));
261
+ } catch { closeAccount(); /* Recovery storage faults are non-grant causes. */ }
262
+ }
263
+ async function persist(raw: string | null, ledger: Ledger, affectedKey?: string, grantCause?: Request): Promise<"saved" | "conflict" | "uncertain"> {
264
+ try {
265
+ const next = canonical(ledger);
266
+ if (!await input.storage.compareAndSwap(accountKey, raw, next)) return "conflict";
267
+ if (await input.storage.get(accountKey) !== next) { await quarantineAccount(affectedKey, grantCause); return "uncertain"; }
268
+ return "saved";
269
+ } catch { await quarantineAccount(affectedKey, grantCause); return "uncertain"; }
270
+ }
271
+ async function releaseSlot(r?: Request): Promise<boolean> {
272
+ try {
273
+ const release = await load();
274
+ if (r ? availabilityQuarantined(r) : quarantine || availability.quarantined) return false;
275
+ release.ledger.slot = false;
276
+ if (await persist(release.raw, release.ledger) === "saved") return true;
277
+ } catch { /* The durable slot stays held until host repair. */ }
278
+ await quarantineAccount(); return false;
279
+ }
280
+ function current(r: Request, snapshot?: Subject): Subject | Reason {
281
+ const s = input.subjectMapper(r.subjectReference);
282
+ if (!s || s.subjectReference !== r.subjectReference || !equal(s.binding, binding)) return "identity_conflict";
283
+ if (s.generation !== r.generation) return "stale";
284
+ if (r.deadlineMs <= now()) return "deadline";
285
+ if (s.erased && (r.operation === "profile.sync" || r.operation === "consent.push" && r.payload.action === "grant")) return "restricted";
286
+ if (snapshot && !equal(s, snapshot)) return "provenance";
287
+ return copy(s);
288
+ }
289
+ function authority(r: Request, target: Subject): Reason | undefined {
290
+ const auth = input.authorizations.get(r.authorizationReference);
291
+ if (!auth || !equal(auth.binding, binding) || auth.subjectReference !== r.subjectReference || !equal(auth.audience, r.audience) || auth.operation !== r.operation) return "provenance";
292
+ if (auth.expiresAtMs <= now()) return "deadline";
293
+ if (r.operation === "consent.read" || r.operation === "erasure.read") return;
294
+ const ref = r.operation === "erasure.request" ? r.payload.authorityReference : r.payload.consentEvidenceReference;
295
+ const local = input.localEvidence.get(ref);
296
+ if (!local || !equal(local.binding, binding) || local.subjectReference !== r.subjectReference || !equal(local.audience, r.audience) || !text(local.purpose)) return "provenance";
297
+ const action = r.operation === "profile.sync" ? "profile" : r.operation === "erasure.request" ? "erase" : r.payload.action;
298
+ if (!local.current || local.action !== action) return "restricted";
299
+ if (r.operation === "profile.sync") {
300
+ if (!equal(local.projection, r.payload.projection)) return "provenance";
301
+ if (r.payload.projection.email !== target.email || r.payload.projection.externalReference !== target.externalReference ||
302
+ Object.keys(r.payload.projection.fields).some(k => !target.ownedFields.includes(k as keyof ProfileProjection["fields"])) ||
303
+ Object.keys(r.payload.projection.properties).some(k => !target.ownedProperties.includes(k))) return "identity_conflict";
304
+ if (!verification?.noTrigger.profile) return "restricted";
305
+ }
306
+ if (r.operation === "erasure.request" && (local.caseReference !== r.payload.caseReference || local.authorityReference !== r.payload.authorityReference)) return "provenance";
307
+ if (r.operation === "consent.push") {
308
+ if (r.payload.action === "withdraw" && r.audience.kind === "account-wide" && !local.allPurposes) return "restricted";
309
+ if (r.payload.action === "grant" && (!verification?.noTrigger.accountWideGrant || !verification.noTrigger.doi || !verification.suppressionSafeAtEffect)) return "restricted";
310
+ if (r.payload.action === "withdraw" && r.audience.kind === "audience" && !verification?.safeListRemoval) return "restricted";
311
+ }
312
+ }
313
+ async function http(r: Request, method: string, path: string, body: unknown, guard: () => Reason | undefined): Promise<{ status: number; body?: unknown; failure?: Result; slotHeld?: boolean }> {
314
+ if (body !== undefined && bytes(body) > maxRequestBytes) return { status: 0, failure: refused(r, "invalid_request") };
315
+ const mutation = method !== "GET";
316
+ const ambiguous = () => mutation ? unknown(r, "acceptance_ambiguous", { operation: r.operation, request: r }) : retry(r, "provider");
317
+ let loaded: Awaited<ReturnType<typeof load>>;
318
+ try { loaded = await load(); } catch { closeAccount(); return { status: 0, failure: retry(r, "host") }; }
319
+ let { raw, ledger } = loaded;
320
+ // Pace the remaining HTTP steps of this operation without retrying a mutation.
321
+ // Provider cooldown and occupied/quarantined slots still return immediately.
322
+ const delay = ledger.nextAt - now();
323
+ if (!availabilityQuarantined(r) && !ledger.quarantined && !ledger.slot && ledger.coolingUntil <= now() && delay > 0 && delay < Math.min(timeoutMs, r.deadlineMs - now())) {
324
+ await new Promise<void>(resolve => setTimeout(resolve, delay));
325
+ try { ({ raw, ledger } = await load()); } catch { closeAccount(); return { status: 0, failure: retry(r, "host") }; }
326
+ }
327
+ if ((availabilityQuarantined(r) || ledger.quarantined || ledger.slot) || ledger.coolingUntil > now() || ledger.nextAt > now()) return { status: 0, failure: retry(r, "cooling", Math.max(1, Math.ceil((Math.max(ledger.coolingUntil, ledger.nextAt) - now()) / 1000))) };
328
+ if (mutation && (r.operation === "profile.sync" || r.operation === "consent.push" && r.payload.action === "grant") && ledger.tombstones.includes(subjectKey(r))) return { status: 0, failure: refused(r, "restricted") };
329
+ if (mutation && r.operation === "consent.push" && r.payload.action === "grant" && Object.values(ledger.withdrawals).some(w => w.owner === subjectKey(r) && !w.resolved && overlaps(w.audience, r.audience))) return { status: 0, failure: refused(r, "restricted") };
330
+ const before = guard(); if (before) return { status: 0, failure: refused(r, before) };
331
+ ledger.nextAt = now() + rateIntervalMs; ledger.slot = true;
332
+ const saved = await persist(raw, ledger);
333
+ if (saved !== "saved") return { status: 0, failure: saved === "conflict" ? retry(r, "host") : unknown(r, "dispatch_unresolved") };
334
+ async function transfer(): Promise<{ status: number; body?: unknown; failure?: Result }> {
335
+ const controller = new AbortController();
336
+ const remaining = Math.min(timeoutMs, r.deadlineMs - now());
337
+ let timeout: ReturnType<typeof setTimeout> | undefined;
338
+ const abort = new Promise<never>((_resolve, reject) => { timeout = setTimeout(() => { controller.abort(); reject(new Error("deadline")); }, remaining); });
339
+ try {
340
+ if (remaining <= 0) return { status: 0, failure: refused(r, "deadline") };
341
+ const recheck = guard(); if (recheck) return { status: 0, failure: refused(r, recheck) };
342
+ if (availabilityQuarantined(r) || !availability.busy) return { status: 0, failure: retry(r, "cooling") };
343
+ let credential: ReturnType<Configuration["resolveCredential"]>;
344
+ try { credential = input.resolveCredential(); } catch { return { status: 0, failure: retry(r, "host") }; }
345
+ if (!credential || !equal(credential.binding, binding) || !text(credential.key) || /\s/.test(credential.key)) return { status: 0, failure: refused(r, "binding") };
346
+ const response = await Promise.race([(input.fetch ?? fetch)(ORIGIN + path, {
347
+ method, ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: controller.signal, redirect: "manual",
348
+ headers: { Authorization: `Klaviyo-API-Key ${credential.key}`, revision: REVISION, Accept: "application/vnd.api+json",
349
+ ...(body === undefined ? {} : { "Content-Type": "application/vnd.api+json" }) },
350
+ }), abort]);
351
+ if (response.status === 429) {
352
+ const value = response.headers.get("retry-after");
353
+ const parsed = value && /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : value ? (Date.parse(value) - now()) / 1000 : NaN;
354
+ const afterSeconds = Number.isFinite(parsed) && parsed >= 0 && Number.isFinite(now() + Math.ceil(parsed) * 1000) ? Math.max(1, Math.ceil(parsed)) : 60;
355
+ // A lost cooldown write must never turn the withdrawal exemption into
356
+ // permission to ignore an observed account-level provider rate limit.
357
+ providerCooling.set(availability, Math.max(providerCooling.get(availability) ?? 0, now() + afterSeconds * 1000));
358
+ try {
359
+ const next = await load(); next.ledger.coolingUntil = Math.max(next.ledger.coolingUntil, now() + afterSeconds * 1000);
360
+ if (await persist(next.raw, next.ledger) !== "saved") closeAccount();
361
+ } catch { closeAccount(); }
362
+ await response.body?.cancel().catch(() => {});
363
+ return { status: 429, failure: retry(r, "cooling", afterSeconds) };
364
+ }
365
+ if (response.status >= 400 && response.status < 500) {
366
+ await response.body?.cancel().catch(() => {});
367
+ if (response.status === 404 && !mutation) return { status: 404 };
368
+ return { status: response.status, failure: refused(r, response.status === 409 ? "identity_conflict" : "provider_refusal") };
369
+ }
370
+ if (response.status >= 300) { await response.body?.cancel().catch(() => {}); return { status: response.status, failure: ambiguous() }; }
371
+ if (response.status === 202 || response.status === 204) { await response.body?.cancel().catch(() => {}); return { status: response.status }; }
372
+ if (!/^application\/(?:vnd\.api\+json|json)(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "")) { await response.body?.cancel().catch(() => {}); return { status: response.status, failure: ambiguous() }; }
373
+ const reader = response.body?.getReader();
374
+ if (!reader) return { status: response.status, failure: ambiguous() };
375
+ const chunks: Uint8Array[] = []; let size = 0;
376
+ for (;;) {
377
+ const chunk = await Promise.race([reader.read(), abort]);
378
+ if (chunk.done) break;
379
+ size += chunk.value.byteLength;
380
+ if (size > maxResponseBytes) { await reader.cancel(); return { status: response.status, failure: ambiguous() }; }
381
+ chunks.push(chunk.value);
382
+ }
383
+ const combined = new Uint8Array(size); let offset = 0;
384
+ for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.byteLength; }
385
+ return { status: response.status, body: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(combined)) };
386
+ } catch { return { status: 0, failure: ambiguous() }; }
387
+ finally {
388
+ clearTimeout(timeout);
389
+ if (r.operation !== "consent.read") await releaseSlot(r);
390
+ }
391
+ }
392
+ const answer = await transfer();
393
+ return { ...answer, ...(r.operation === "consent.read" ? { slotHeld: true } : {}) };
394
+ }
395
+ async function execute(original: Request, ownsAvailability: boolean): Promise<Result> {
396
+ // Snapshot validation precedes every await, credential lookup and provider call.
397
+ let r: Request;
398
+ try { r = copy(original); if (!validRequest(r, maxRequestBytes)) return refused(r, "invalid_request"); }
399
+ catch { return refused(original, "invalid_request"); }
400
+ if (!equal(r.binding, binding)) return refused(r, "binding");
401
+ const initial = current(r); if (typeof initial === "string") return refused(r, initial);
402
+ const key = keyOf(r), owner = subjectKey(r);
403
+ let loaded: Awaited<ReturnType<typeof load>>;
404
+ try { loaded = await load(); } catch (error) { closeAccount(); return error instanceof Error && error.message === "journal" ? unknown(r, "record_failed") : retry(r, "host"); }
405
+ let { raw, ledger } = loaded;
406
+ // Receipt provenance precedes capability refusal, including deserialized receipts.
407
+ if (r.operation === "erasure.read") {
408
+ const receipt = r.payload.receipt;
409
+ if (!receipt?.request || !validRequest(receipt.request, maxRequestBytes)) return refused(r, "provenance");
410
+ const prior = ledger.entries[keyOf(receipt.request)];
411
+ if (receipt.operation !== "erasure.request" || !prior || !equal(prior.request, receipt.request) || prior.request.operation !== "erasure.request" ||
412
+ !equal(prior.request.binding, r.binding) || prior.request.subjectReference !== r.subjectReference || !equal(prior.request.audience, r.audience) ||
413
+ !prior.result || !("receipt" in prior.result) || !equal(prior.result.receipt, receipt)) return refused(r, "provenance");
414
+ }
415
+ const latest = current(r, initial); if (typeof latest === "string") return refused(r, latest);
416
+ if (ledger.entries[key] && ledger.entries[key].semantic !== semantic(r)) return refused(r, "invalid_request");
417
+ if (r.operation !== "consent.push" && !equal(r.audience, audience) || r.operation === "consent.push" && r.audience.kind === "audience" && !equal(r.audience, audience)) return refused(r, "unsupported");
418
+ const cap = capabilities.operations[r.operation];
419
+ if (cap.state !== "supported") return refused(r, cap.state);
420
+ const previous = ledger.entries[key];
421
+ if (previous) {
422
+ if (previous.semantic !== semantic(r)) return refused(r, "invalid_request");
423
+ if (!equal(previous.request.binding, r.binding) || previous.request.subjectReference !== r.subjectReference || previous.request.operation !== r.operation) { closeAccount(); return unknown(r, "record_failed"); }
424
+ if (previous.result) {
425
+ const value = { ...previous.result };
426
+ if (r.operation === "consent.push" && (value.kind === "accepted" || value.kind === "acknowledged") && "reconcileRequired" in value) {
427
+ value.reconcileRequired ||= Object.values(ledger.withdrawals).some(w => !w.resolved && w.owner === owner && overlaps(w.audience, r.audience) && ledger.entries[key]?.request.subjectReference === r.subjectReference);
428
+ }
429
+ return result(r, value);
430
+ }
431
+ return previous.phase === "admitted" ? result(r, { kind: "accepted", receipt: { operation: r.operation, request: previous.request }, journal: "confirmed", ...(r.operation === "consent.push" ? { reconcileRequired: true } : {}) }) : unknown(r, "dispatch_unresolved");
432
+ }
433
+ if (r.operation === "consent.push" && r.payload.action === "grant" && Object.values(ledger.withdrawals).some(w => !w.resolved && w.owner === owner && overlaps(w.audience, r.audience))) return refused(r, "restricted");
434
+ // Withdrawals omit only this subject's unresolved-grant fence.
435
+ const pending = Object.values(ledger.entries).filter(e => subjectKey(e.request) === owner && e.phase !== "settled");
436
+ const withdrawing = r.operation === "consent.push" && r.payload.action === "withdraw";
437
+ if (r.operation !== "consent.read" && pending.some(e => !withdrawing || e.request.operation !== "consent.push" || e.request.payload.action !== "grant")) return unknown(r, "dispatch_unresolved");
438
+ if ((availabilityQuarantined(r) || ledger.quarantined || ledger.slot) || ledger.coolingUntil > now()) return retry(r, "cooling", Math.max(1, Math.ceil((ledger.coolingUntil - now()) / 1000)));
439
+ const proof = authority(r, latest); if (proof) return refused(r, proof);
440
+ if (!ownsAvailability) return retry(r, "cooling");
441
+ if (ledger.tombstones.includes(owner) && (r.operation === "profile.sync" || r.operation === "consent.push" && r.payload.action === "grant")) return refused(r, "restricted");
442
+ const localRef = r.operation === "profile.sync" || r.operation === "consent.push" ? r.payload.consentEvidenceReference : r.operation === "erasure.request" ? r.payload.authorityReference : undefined;
443
+ const localSnapshot = localRef ? copy(input.localEvidence.get(localRef)) : undefined;
444
+ const authSnapshot = copy(input.authorizations.get(r.authorizationReference));
445
+ const guard = (): Reason | undefined => {
446
+ const s = current(r, initial); if (typeof s === "string") return s;
447
+ if (!equal(authSnapshot, input.authorizations.get(r.authorizationReference)) || localRef && !equal(localSnapshot, input.localEvidence.get(localRef))) return "provenance";
448
+ return authority(r, s);
449
+ };
450
+ const listId = r.audience.kind === "audience" ? Object.entries(verification!.listMappings).filter(([, scope]) => equal(scope, r.audience)).map(([k]) => k) : [];
451
+ let target = copy({ ...latest, ...(ledger.mappings[owner] ? { id: ledger.mappings[owner] } : {}) });
452
+ if (target.id && !id(target.id) || target.id && latest.id && target.id !== latest.id) return refused(r, "identity_conflict");
453
+ const readProfile = async (selected: Subject) => http(r, "GET", `/api/profiles/${selected.id}?additional-fields[profile]=subscriptions`, undefined, guard);
454
+ if (!target.id) {
455
+ if (r.operation !== "profile.sync") return refused(r, "identity_conflict");
456
+ const query = new URLSearchParams({ filter: `and(equals(email,${JSON.stringify(target.email)}),equals(external_id,${JSON.stringify(target.externalReference)}))`, "page[size]": "2" });
457
+ const lookup = await http(r, "GET", "/api/profiles?" + query, undefined, guard);
458
+ if (lookup.failure) return lookup.failure;
459
+ const root = object(lookup.body), data = root.data;
460
+ if (lookup.status !== 200 || !Array.isArray(data) || data.length > 1 || object(root.links).next !== null) return refused(r, "identity_conflict");
461
+ if (data.length === 1) {
462
+ const found = profileData({ data: data[0] }, target);
463
+ if (!found) return refused(r, "identity_conflict");
464
+ target = copy({ ...target, id: found });
465
+ } else if (!verification!.safeCreate || !verification!.noTrigger.profile) return refused(r, "restricted");
466
+ }
467
+ if (target.id && (r.operation === "profile.sync" || r.operation === "consent.push")) {
468
+ const read = await readProfile(target);
469
+ if (read.failure) return read.failure;
470
+ if (read.status !== 200 || !profileData(read.body, target)) return refused(r, "identity_conflict");
471
+ if (r.operation === "consent.push" && r.payload.action === "grant") {
472
+ const value = consentValue(read.body, target, r.audience, verification!, now(), maxFeedbackItems);
473
+ if (!value || value.coverage !== "complete" || value.feedback.length > 0) return refused(r, "restricted");
474
+ }
475
+ }
476
+ if (r.operation === "consent.read") {
477
+ const readStartedAt = now();
478
+ const settlements = copy(Object.fromEntries(input.grantSettlements ?? []));
479
+ const read = await readProfile(target);
480
+ if (read.failure) {
481
+ if (read.slotHeld) await releaseSlot();
482
+ const changed = guard(); return changed ? refused(r, changed) : read.failure;
483
+ }
484
+ const changed = guard(); if (changed) { if (read.slotHeld) await releaseSlot(); return refused(r, changed); }
485
+ let value = read.status === 404 ? copy({ audience: r.audience, observedAtMs: now(), remoteConsent: "unknown" as const, coverage: "partial" as const, feedback: [] }) : consentValue(read.body, target, r.audience, verification!, now(), maxFeedbackItems);
486
+ if (!value) { if (read.slotHeld) await releaseSlot(); const changed = guard(); return changed ? refused(r, changed) : retry(r, "provider"); }
487
+ const unsettled = (w: Ledger["withdrawals"][string]) => w.earlier.some(k => {
488
+ const grant = ledger.entries[k];
489
+ const proof = grant && settlements[grant.request.requestId];
490
+ return !grant || !proof || proof.source !== "provider-read" || !text(proof.evidenceReference) ||
491
+ !equal(proof.binding, r.binding) || proof.subjectReference !== r.subjectReference ||
492
+ proof.requestId !== grant.request.requestId || !Number.isFinite(proof.settledAtMs) || proof.settledAtMs > readStartedAt;
493
+ });
494
+ const affected = Object.values(ledger.withdrawals).filter(w => w.owner === owner && !w.resolved && overlaps(w.audience, r.audience));
495
+ const uncertain = affected.some(w => unsettled(w)) || affected.length > 0 && (value.coverage !== "complete" || !["subscribed", "unsubscribed"].includes(value.remoteConsent));
496
+ if (uncertain) value = copy({ ...value, remoteConsent: "unknown" });
497
+ let journal: "confirmed" | "unknown" = "unknown";
498
+ try {
499
+ const next = await load();
500
+ if (!uncertain && value.coverage === "complete") {
501
+ for (const [k, w] of Object.entries(next.ledger.withdrawals)) {
502
+ if (w.owner === owner && overlaps(w.audience, r.audience) && !unsettled(w)) {
503
+ if (value.remoteConsent === "unsubscribed") next.ledger.withdrawals[k].resolved = true;
504
+ for (const earlier of w.earlier) { next.ledger.entries[earlier].grantSettled = true; next.ledger.entries[earlier].phase = "settled"; }
505
+ }
506
+ }
507
+ }
508
+ const alreadyQuarantined = next.ledger.quarantined;
509
+ next.ledger.entries[key] = { request: r, semantic: semantic(r), target, result: result(r, { kind: "completed", value, journal: "unknown" }), phase: "settled" };
510
+ next.ledger.quarantined = true;
511
+ if (await persist(next.raw, next.ledger, key) === "saved" && !alreadyQuarantined) {
512
+ const recorded = canonical(next.ledger);
513
+ next.ledger.entries[key].result = result(r, { kind: "completed", value, journal: "confirmed" });
514
+ next.ledger.quarantined = false; next.ledger.slot = false;
515
+ if (await persist(recorded, next.ledger, key) === "saved") journal = "confirmed";
516
+ else await quarantineAccount(key);
517
+ } else await quarantineAccount(key);
518
+ } catch { await quarantineAccount(key); }
519
+ const afterPersistence = guard(); if (afterPersistence) return refused(r, afterPersistence);
520
+ return result(r, { kind: "completed", value, journal });
521
+ }
522
+ if (r.operation === "erasure.read") return refused(r, "unsupported");
523
+ if (r.operation === "consent.push" && r.audience.kind === "audience" && (listId.length !== 1 || !id(listId[0]))) return refused(r, "restricted");
524
+ const changed = guard(); if (changed) return refused(r, changed);
525
+ // Refresh before CAS: all namespaces/instances sharing account storage arbitrate here.
526
+ ({ raw, ledger } = await load());
527
+ if (ledger.entries[key]) return unknown(r, "dispatch_unresolved");
528
+ if (Object.values(ledger.entries).some(e => subjectKey(e.request) === owner && e.phase !== "settled" && (!withdrawing || e.request.operation !== "consent.push" || e.request.payload.action !== "grant"))) return unknown(r, "dispatch_unresolved");
529
+ if (r.operation === "consent.push" && r.payload.action === "grant" && Object.values(ledger.withdrawals).some(w => !w.resolved && w.owner === owner && overlaps(w.audience, r.audience))) return refused(r, "restricted");
530
+ const receipt = copy({ operation: r.operation, request: r });
531
+ ledger.entries[key] = { request: r, semantic: semantic(r), target, phase: "dispatched" };
532
+ if (r.operation === "erasure.request" && !ledger.tombstones.includes(owner)) ledger.tombstones.push(owner);
533
+ let reconcileRequired = false;
534
+ if (withdrawing) {
535
+ const earlier = Object.entries(ledger.entries).filter(([, e]) => subjectKey(e.request) === owner && e.request.operation === "consent.push" && e.request.payload.action === "grant" && !e.grantSettled).map(([k]) => k);
536
+ const causes = quarantineCauses.get(availability);
537
+ for (const [grant, subject] of causes?.kind === "grants" ? causes.grants : []) if (subject === owner && !earlier.includes(grant)) earlier.push(grant);
538
+ reconcileRequired = earlier.length > 0;
539
+ ledger.withdrawals[key] = { owner, audience: r.audience, earlier, resolved: false };
540
+ }
541
+ const grantCause = r.operation === "consent.push" && r.payload.action === "grant" ? r : undefined;
542
+ const admitted = await persist(raw, ledger, undefined, grantCause);
543
+ if (admitted !== "saved") return admitted === "conflict" ? retry(r, "host") : unknown(r, "dispatch_unresolved");
544
+ const command = r.operation === "consent.push" ? consentCommand(r, { id: target.id!, email: target.email }, listId[0]) : r.operation === "erasure.request" ? {
545
+ method: "POST", path: "/api/data-privacy-deletion-jobs", expected: [202], body: { data: { type: "data-privacy-deletion-job", attributes: { profile: { data: { type: "profile", id: target.id, attributes: {} } } } } },
546
+ } : {
547
+ method: target.id ? "PATCH" : "POST", path: target.id ? `/api/profiles/${target.id}` : "/api/profiles", expected: target.id ? [200, 202, 204] : [201, 202, 204],
548
+ body: { data: { type: "profile", ...(target.id ? { id: target.id } : {}), attributes: profileAttributes(r.payload.projection, !target.id) } },
549
+ };
550
+ const wire = await http(r, command.method, command.path, command.body, guard);
551
+ let settled: Result, remoteId: string | undefined;
552
+ if (wire.failure) settled = wire.failure;
553
+ else if (!command.expected.includes(wire.status)) settled = unknown(r, "acceptance_ambiguous", receipt);
554
+ else {
555
+ settled = result(r, { kind: "acknowledged", receipt, journal: quarantine || availability.quarantined || ledger.quarantined ? "unknown" : "confirmed", ...(r.operation === "consent.push" ? { reconcileRequired } : {}) });
556
+ if (r.operation === "profile.sync") {
557
+ const found = wire.body === undefined ? target.id : profileData(wire.body, target, r.payload.projection);
558
+ if (wire.body !== undefined && !found) settled = unknown(r, "acceptance_ambiguous", receipt);
559
+ else if (!found) settled = result(r, { kind: "acknowledged", receipt, journal: "unknown" });
560
+ else {
561
+ const verified = await readProfile({ ...target, id: found });
562
+ if (verified.failure || verified.status !== 200 || !profileData(verified.body, { ...target, id: found }, r.payload.projection) || guard()) settled = result(r, { kind: "acknowledged", receipt, journal: "unknown" });
563
+ else remoteId = found;
564
+ }
565
+ }
566
+ }
567
+ try {
568
+ const final = await load(), entry = final.ledger.entries[key];
569
+ if (!entry || !equal(entry.request, r) || !equal(entry.target, target)) { closeAccount(); return settled.kind === "acknowledged" ? result(r, { ...settled, journal: "unknown" }) : unknown(r, "record_failed", receipt); }
570
+ entry.result = settled;
571
+ // Unknown acceptance retains the per-subject dispatch fence. Known acceptance with a
572
+ // failed projection/mapping check quarantines the account; it is never retried as a write.
573
+ entry.phase = settled.kind === "unknown" ? "dispatched" : "settled";
574
+ if (r.operation === "consent.push" && r.payload.action === "grant" && (settled.kind === "refused" || settled.kind === "retryable")) entry.grantSettled = true;
575
+ if (r.operation === "profile.sync" && settled.kind === "acknowledged" && settled.journal === "unknown") final.ledger.quarantined = true;
576
+ if (remoteId) { entry.remoteId = remoteId; final.ledger.mappings[owner] = remoteId; }
577
+ const releaseQuarantine = !final.ledger.quarantined && !availabilityQuarantined(r);
578
+ // A grant already has its durable per-subject dispatch fence. Only other
579
+ // operations need the non-grant quarantine bit during journal settlement.
580
+ final.ledger.quarantined ||= !grantCause;
581
+ if (await persist(final.raw, final.ledger, key, grantCause) !== "saved") {
582
+ await quarantineAccount(key, grantCause);
583
+ return settled.kind === "acknowledged" ? result(r, { ...settled, journal: "unknown" }) : settled.kind === "refused" || settled.kind === "retryable" ? settled : unknown(r, "record_failed", receipt);
584
+ }
585
+ if (releaseQuarantine) {
586
+ const recorded = canonical(final.ledger); final.ledger.quarantined = false;
587
+ if (await persist(recorded, final.ledger, key, grantCause) !== "saved") {
588
+ await quarantineAccount(key, grantCause);
589
+ return settled.kind === "acknowledged" ? result(r, { ...settled, journal: "unknown" }) : settled;
590
+ }
591
+ }
592
+ return settled;
593
+ } catch {
594
+ await quarantineAccount(key);
595
+ return settled.kind === "acknowledged" ? result(r, { ...settled, journal: "unknown" }) : settled.kind === "refused" || settled.kind === "retryable" ? settled : unknown(r, "record_failed", receipt);
596
+ }
597
+ }
598
+ async function run(r: Request, operation: Operation): Promise<Result> {
599
+ const fallback = { binding, operation, subjectReference: "" as Request["subjectReference"], requestId: "", generation: 0 };
600
+ try {
601
+ if (!validRequest(r, maxRequestBytes)) return result(fallback, { kind: "refused", reason: "invalid_request" });
602
+ } catch { return result(fallback, { kind: "refused", reason: "invalid_request" }); }
603
+ if (r.operation !== operation) return result(r, { kind: "refused", reason: "binding" });
604
+ const ownsAvailability = !availability.busy;
605
+ if (ownsAvailability) availability.busy = true;
606
+ try { return await execute(r, ownsAvailability); }
607
+ catch { closeAccount(); return result({ ...fallback, subjectReference: r.subjectReference, requestId: r.requestId, generation: r.generation }, { kind: "unknown", reason: "record_failed" }); }
608
+ finally { if (ownsAvailability) availability.busy = false; }
609
+ }
610
+ const scope = copy(input.legacy?.scope ?? { bindingId: binding.namespace, accountReference: binding.account, audienceReference: audience.kind === "audience" ? audience.reference : "account-wide" });
611
+ function legacyRequest(claim: MarketingAutomationClaim): Request | undefined {
612
+ const saved = input.legacy?.claims.get(claim.effectId);
613
+ return saved && equal(saved.claim, claim) && equal(claim.scope, scope) && claim.subjectReference === saved.request.subjectReference && claim.generation === saved.request.generation &&
614
+ claim.deadlineMs === saved.request.deadlineMs && claim.idempotencyKey === saved.request.requestId ? saved.request : undefined;
615
+ }
616
+ const observation = (claim: MarketingAutomationClaim, value: Result, remoteId?: string): MarketingAutomationObservation => {
617
+ const base = { effectId: claim.effectId, generation: claim.generation, observedAtMs: now() };
618
+ if (value.kind === "acknowledged" && value.journal === "confirmed" && remoteId) return { ...base, status: "accepted", remoteReference: remoteId };
619
+ if (value.kind === "retryable") return { ...base, status: "throttled", retryAfterMs: value.afterSeconds * 1000 };
620
+ if (value.kind === "refused") return { ...base, status: "refused", reason: value.reason === "deadline" ? "deadline" : ["binding", "provenance", "stale", "identity_conflict"].includes(value.reason) ? "scope" : ["unsupported", "verification_pending"].includes(value.reason) ? "unsupported" : "restricted" };
621
+ return { ...base, status: "ambiguous", retryAfterMs: 1000 };
622
+ };
623
+ return {
624
+ capabilities, profiles: { sync: r => run(r, "profile.sync") as Promise<SyncResult<"profile.sync">> },
625
+ consent: { read: r => run(r, "consent.read") as Promise<SyncResult<"consent.read">> },
626
+ erasure: { request: r => run(r, "erasure.request") as Promise<SyncResult<"erasure.request">>, read: r => run(r, "erasure.read") as Promise<SyncResult<"erasure.read">> },
627
+ consentPush: { capabilities, push: r => run(r, "consent.push") as Promise<ConsentPushResult> },
628
+ newsletter: { providerKind: "klaviyo", capabilities: { subscribe: false, unsubscribe: false, suppress: false, unsuppress: false, doubleOptIn: false, emailChange: false, listMembership: false, webhookIdempotency: false, retryAfter: false },
629
+ async sync() { return { status: "skipped", reason: "unsupported" }; }, async reconcile() { return { status: "skipped", reason: "unsupported" }; } },
630
+ scope,
631
+ async admit(claim) {
632
+ try {
633
+ const r = legacyRequest(claim); if (!r || !validRequest(r, maxRequestBytes) || !equal(r.binding, binding)) return "refused";
634
+ const s = current(r); return typeof s === "string" || authority(r, s) || capabilities.operations[r.operation].state !== "supported" ? "refused" : "admitted";
635
+ } catch { return "refused"; }
636
+ },
637
+ async apply(claim) {
638
+ const r = legacyRequest(claim); if (!r) return { effectId: claim.effectId, generation: claim.generation, observedAtMs: now(), status: "refused", reason: "scope" };
639
+ const value = await run(r, r.operation), saved = await load().catch(() => null);
640
+ return observation(claim, value, saved?.ledger.entries[keyOf(r)]?.remoteId);
641
+ },
642
+ async observe(claim) {
643
+ const r = legacyRequest(claim); if (!r) return { effectId: claim.effectId, generation: claim.generation, observedAtMs: now(), status: "refused", reason: "scope" };
644
+ try {
645
+ const saved = (await load()).ledger.entries[keyOf(r)]; return saved?.result ? observation(claim, saved.result, saved.remoteId) : null;
646
+ } catch { return observation(claim, unknown(r, "record_failed")); }
647
+ },
648
+ };
649
+ }