@openlup/platform 0.21.0 → 0.23.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 (349) hide show
  1. package/CHANGELOG.md +432 -146
  2. package/README.md +82 -589
  3. package/dist/composition/composeBundle.d.ts +7 -0
  4. package/dist/composition/composeBundle.js +2 -0
  5. package/dist/composition/fulfillmentContribution.d.ts +21 -0
  6. package/dist/composition/fulfillmentContribution.js +19 -0
  7. package/dist/composition/index.d.ts +1 -0
  8. package/dist/composition/index.js +1 -0
  9. package/dist/fulfillment/adapterConformance.d.ts +3 -0
  10. package/dist/fulfillment/adapterConformance.js +223 -0
  11. package/dist/fulfillment/bindings.d.ts +3 -0
  12. package/dist/fulfillment/bindings.js +29 -0
  13. package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
  14. package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
  15. package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
  16. package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
  17. package/dist/fulfillment/contracts/alignment.d.ts +25 -0
  18. package/dist/fulfillment/contracts/alignment.js +1 -0
  19. package/dist/fulfillment/contracts/bindings.d.ts +51 -0
  20. package/dist/fulfillment/contracts/bindings.js +1 -0
  21. package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
  22. package/dist/fulfillment/contracts/capabilities.js +1 -0
  23. package/dist/fulfillment/contracts/conformance.d.ts +95 -0
  24. package/dist/fulfillment/contracts/conformance.js +1 -0
  25. package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
  26. package/dist/fulfillment/contracts/customerAlignment.js +1 -0
  27. package/dist/fulfillment/contracts/dispatch.d.ts +143 -0
  28. package/dist/fulfillment/contracts/dispatch.js +1 -0
  29. package/dist/fulfillment/contracts/index.d.ts +18 -0
  30. package/dist/fulfillment/contracts/index.js +1 -0
  31. package/dist/fulfillment/contracts/label.d.ts +35 -0
  32. package/dist/fulfillment/contracts/label.js +1 -0
  33. package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
  34. package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
  35. package/dist/fulfillment/contracts/observation.d.ts +72 -0
  36. package/dist/fulfillment/contracts/observation.js +1 -0
  37. package/dist/fulfillment/contracts/operator.d.ts +64 -0
  38. package/dist/fulfillment/contracts/operator.js +1 -0
  39. package/dist/fulfillment/contracts/read.d.ts +110 -0
  40. package/dist/fulfillment/contracts/read.js +1 -0
  41. package/dist/fulfillment/contracts/readback.d.ts +59 -0
  42. package/dist/fulfillment/contracts/readback.js +1 -0
  43. package/dist/fulfillment/contracts/replacement.d.ts +34 -0
  44. package/dist/fulfillment/contracts/replacement.js +1 -0
  45. package/dist/fulfillment/contracts/store.d.ts +89 -0
  46. package/dist/fulfillment/contracts/store.js +1 -0
  47. package/dist/fulfillment/contracts/sync.d.ts +226 -0
  48. package/dist/fulfillment/contracts/sync.js +1 -0
  49. package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
  50. package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
  51. package/dist/fulfillment/delayReadModel.d.ts +11 -0
  52. package/dist/fulfillment/delayReadModel.js +45 -0
  53. package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
  54. package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
  55. package/dist/fulfillment/dispatchProjection.d.ts +26 -0
  56. package/dist/fulfillment/dispatchProjection.js +107 -0
  57. package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
  58. package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
  59. package/dist/fulfillment/handlerSchemas.d.ts +647 -0
  60. package/dist/fulfillment/handlerSchemas.js +213 -0
  61. package/dist/fulfillment/handlerSupport.d.ts +33 -0
  62. package/dist/fulfillment/handlerSupport.js +25 -0
  63. package/dist/fulfillment/index.d.ts +38 -0
  64. package/dist/fulfillment/index.js +37 -0
  65. package/dist/fulfillment/labelLifecycle.d.ts +24 -0
  66. package/dist/fulfillment/labelLifecycle.js +9 -0
  67. package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
  68. package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
  69. package/dist/fulfillment/migrations.d.ts +32 -0
  70. package/dist/fulfillment/migrations.js +9 -0
  71. package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
  72. package/dist/fulfillment/nativeUnsupported.js +18 -0
  73. package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
  74. package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
  75. package/dist/fulfillment/observationProjection.d.ts +32 -0
  76. package/dist/fulfillment/observationProjection.js +90 -0
  77. package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
  78. package/dist/fulfillment/omnipack/capabilities.js +16 -0
  79. package/dist/fulfillment/omnipack/client.d.ts +39 -0
  80. package/dist/fulfillment/omnipack/client.js +172 -0
  81. package/dist/fulfillment/omnipack/index.d.ts +55 -0
  82. package/dist/fulfillment/omnipack/index.js +408 -0
  83. package/dist/fulfillment/omnipack/mappers.d.ts +79 -0
  84. package/dist/fulfillment/omnipack/mappers.js +199 -0
  85. package/dist/fulfillment/omnipack/observation.d.ts +28 -0
  86. package/dist/fulfillment/omnipack/observation.js +72 -0
  87. package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
  88. package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
  89. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
  90. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
  91. package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
  92. package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
  93. package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
  94. package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
  95. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
  96. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
  97. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
  98. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
  99. package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
  100. package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
  101. package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
  102. package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
  103. package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
  104. package/dist/fulfillment/omnipack/providerError.js +47 -0
  105. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  106. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  107. package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
  108. package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
  109. package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
  110. package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
  111. package/dist/fulfillment/paidHandoff.d.ts +27 -0
  112. package/dist/fulfillment/paidHandoff.js +86 -0
  113. package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
  114. package/dist/fulfillment/paidHandoffPorts.js +3 -0
  115. package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
  116. package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
  117. package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
  118. package/dist/fulfillment/postgresDispatchStore.js +44 -0
  119. package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
  120. package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
  121. package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
  122. package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
  123. package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
  124. package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
  125. package/dist/fulfillment/postgresShipmentSpine.d.ts +70 -0
  126. package/dist/fulfillment/postgresShipmentSpine.js +246 -0
  127. package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
  128. package/dist/fulfillment/postgresStockSyncPort.js +66 -0
  129. package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
  130. package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
  131. package/dist/fulfillment/providerCapabilities.d.ts +9 -0
  132. package/dist/fulfillment/providerCapabilities.js +17 -0
  133. package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
  134. package/dist/fulfillment/providerCommandLedger.js +14 -0
  135. package/dist/fulfillment/providerDispatchAcceptance.d.ts +27 -0
  136. package/dist/fulfillment/providerDispatchAcceptance.js +29 -0
  137. package/dist/fulfillment/providerDispatchError.d.ts +6 -0
  138. package/dist/fulfillment/providerDispatchError.js +5 -0
  139. package/dist/fulfillment/providerDispatchWorker.d.ts +44 -0
  140. package/dist/fulfillment/providerDispatchWorker.js +199 -0
  141. package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
  142. package/dist/fulfillment/providerProductSyncWorker.js +122 -0
  143. package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
  144. package/dist/fulfillment/providerReconciliationError.js +17 -0
  145. package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
  146. package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
  147. package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
  148. package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
  149. package/dist/fulfillment/providerReconciliationWorker.d.ts +90 -0
  150. package/dist/fulfillment/providerReconciliationWorker.js +207 -0
  151. package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
  152. package/dist/fulfillment/providerStockAvailability.js +51 -0
  153. package/dist/fulfillment/providerStockSyncWorker.d.ts +41 -0
  154. package/dist/fulfillment/providerStockSyncWorker.js +295 -0
  155. package/dist/fulfillment/providerTrackingReferences.d.ts +16 -0
  156. package/dist/fulfillment/providerTrackingReferences.js +23 -0
  157. package/dist/fulfillment/replacementRefusals.d.ts +5 -0
  158. package/dist/fulfillment/replacementRefusals.js +38 -0
  159. package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
  160. package/dist/fulfillment/replacementShipmentHandler.js +46 -0
  161. package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
  162. package/dist/fulfillment/schemaCapabilities.js +7 -0
  163. package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
  164. package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
  165. package/dist/fulfillment/statusMap.d.ts +2 -0
  166. package/dist/fulfillment/statusMap.js +1 -0
  167. package/dist/fulfillment/trackingProgress.d.ts +1 -0
  168. package/dist/fulfillment/trackingProgress.js +1 -0
  169. package/dist/fulfillment/verifiedIngress.d.ts +79 -0
  170. package/dist/fulfillment/verifiedIngress.js +94 -0
  171. package/dist/invoicing/capabilities.d.ts +11 -0
  172. package/dist/invoicing/capabilities.js +10 -0
  173. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  174. package/dist/invoicing/contracts/capabilities.js +1 -0
  175. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  176. package/dist/invoicing/contracts/companyLookup.js +1 -0
  177. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  178. package/dist/invoicing/contracts/delivery.js +1 -0
  179. package/dist/invoicing/contracts/facts.d.ts +100 -0
  180. package/dist/invoicing/contracts/facts.js +1 -0
  181. package/dist/invoicing/contracts/host.d.ts +246 -0
  182. package/dist/invoicing/contracts/host.js +1 -0
  183. package/dist/invoicing/contracts/index.d.ts +16 -0
  184. package/dist/invoicing/contracts/index.js +1 -0
  185. package/dist/invoicing/contracts/provider.d.ts +126 -0
  186. package/dist/invoicing/contracts/provider.js +1 -0
  187. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  188. package/dist/invoicing/contracts/settlement.js +1 -0
  189. package/dist/invoicing/contracts/store.d.ts +267 -0
  190. package/dist/invoicing/contracts/store.js +1 -0
  191. package/dist/invoicing/index.d.ts +1 -0
  192. package/dist/invoicing/index.js +1 -0
  193. package/dist/payments/stripe/index.d.ts +25 -0
  194. package/dist/payments/stripe/index.js +25 -0
  195. package/dist/payments/tpay/index.d.ts +30 -0
  196. package/dist/payments/tpay/index.js +30 -0
  197. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  198. package/dist/subscriptions/automaticRenewal.js +81 -0
  199. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  200. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  201. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  202. package/dist/subscriptions/contracts/commands.js +1 -0
  203. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  204. package/dist/subscriptions/contracts/dunning.js +1 -0
  205. package/dist/subscriptions/contracts/index.d.ts +6 -0
  206. package/dist/subscriptions/contracts/index.js +1 -0
  207. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  208. package/dist/subscriptions/contracts/messages.js +1 -0
  209. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  210. package/dist/subscriptions/contracts/renewal.js +1 -0
  211. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  212. package/dist/subscriptions/contracts/stockWait.js +1 -0
  213. package/dist/subscriptions/index.d.ts +39 -0
  214. package/dist/subscriptions/index.js +17 -0
  215. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  216. package/dist/subscriptions/paymentOutcomes.js +27 -0
  217. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  218. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  219. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  220. package/dist/subscriptions/postgresRetryStore.js +142 -0
  221. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  222. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  223. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  224. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  225. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  226. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  227. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  228. package/dist/subscriptions/renewalIdentity.js +4 -0
  229. package/package.json +37 -2
  230. package/release-gates.json +97 -3
  231. package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
  232. package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
  233. package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
  234. package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
  235. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  236. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  237. package/src/composition/composeBundle.ts +4 -0
  238. package/src/composition/fulfillmentContribution.ts +28 -0
  239. package/src/composition/index.ts +1 -0
  240. package/src/fulfillment/adapterConformance.ts +195 -0
  241. package/src/fulfillment/bindings.ts +26 -0
  242. package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
  243. package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
  244. package/src/fulfillment/contracts/alignment.ts +13 -0
  245. package/src/fulfillment/contracts/bindings.ts +30 -0
  246. package/src/fulfillment/contracts/capabilities.ts +15 -0
  247. package/src/fulfillment/contracts/conformance.ts +63 -0
  248. package/src/fulfillment/contracts/customerAlignment.ts +4 -0
  249. package/src/fulfillment/contracts/dispatch.ts +73 -0
  250. package/src/fulfillment/contracts/index.ts +18 -0
  251. package/src/fulfillment/contracts/label.ts +9 -0
  252. package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
  253. package/src/fulfillment/contracts/observation.ts +26 -0
  254. package/src/fulfillment/contracts/operator.ts +31 -0
  255. package/src/fulfillment/contracts/read.ts +42 -0
  256. package/src/fulfillment/contracts/readback.ts +24 -0
  257. package/src/fulfillment/contracts/replacement.ts +15 -0
  258. package/src/fulfillment/contracts/store.ts +27 -0
  259. package/src/fulfillment/contracts/sync.ts +72 -0
  260. package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
  261. package/src/fulfillment/delayReadModel.ts +60 -0
  262. package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
  263. package/src/fulfillment/dispatchProjection.ts +105 -0
  264. package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
  265. package/src/fulfillment/handlerSchemas.ts +255 -0
  266. package/src/fulfillment/handlerSupport.ts +25 -0
  267. package/src/fulfillment/index.ts +39 -0
  268. package/src/fulfillment/labelLifecycle.ts +11 -0
  269. package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
  270. package/src/fulfillment/migrations.ts +9 -0
  271. package/src/fulfillment/nativeUnsupported.ts +23 -0
  272. package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
  273. package/src/fulfillment/observationProjection.ts +92 -0
  274. package/src/fulfillment/omnipack/capabilities.ts +18 -0
  275. package/src/fulfillment/omnipack/client.ts +270 -0
  276. package/src/fulfillment/omnipack/index.ts +369 -0
  277. package/src/fulfillment/omnipack/mappers.ts +282 -0
  278. package/src/fulfillment/omnipack/observation.ts +79 -0
  279. package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
  280. package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
  281. package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
  282. package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
  283. package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
  284. package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
  285. package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
  286. package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
  287. package/src/fulfillment/omnipack/providerError.ts +64 -0
  288. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  289. package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
  290. package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
  291. package/src/fulfillment/paidHandoff.ts +122 -0
  292. package/src/fulfillment/paidHandoffPorts.ts +21 -0
  293. package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
  294. package/src/fulfillment/postgresDispatchStore.ts +55 -0
  295. package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
  296. package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
  297. package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
  298. package/src/fulfillment/postgresShipmentSpine.ts +338 -0
  299. package/src/fulfillment/postgresStockSyncPort.ts +68 -0
  300. package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
  301. package/src/fulfillment/providerCapabilities.ts +18 -0
  302. package/src/fulfillment/providerCommandLedger.ts +53 -0
  303. package/src/fulfillment/providerDispatchAcceptance.ts +37 -0
  304. package/src/fulfillment/providerDispatchError.ts +7 -0
  305. package/src/fulfillment/providerDispatchWorker.ts +159 -0
  306. package/src/fulfillment/providerProductSyncWorker.ts +154 -0
  307. package/src/fulfillment/providerReconciliationError.ts +20 -0
  308. package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
  309. package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
  310. package/src/fulfillment/providerReconciliationWorker.ts +206 -0
  311. package/src/fulfillment/providerStockAvailability.ts +60 -0
  312. package/src/fulfillment/providerStockSyncWorker.ts +318 -0
  313. package/src/fulfillment/providerTrackingReferences.ts +32 -0
  314. package/src/fulfillment/replacementRefusals.ts +43 -0
  315. package/src/fulfillment/replacementShipmentHandler.ts +47 -0
  316. package/src/fulfillment/schemaCapabilities.ts +7 -0
  317. package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
  318. package/src/fulfillment/statusMap.ts +2 -0
  319. package/src/fulfillment/trackingProgress.ts +1 -0
  320. package/src/fulfillment/verifiedIngress.ts +113 -0
  321. package/src/invoicing/capabilities.ts +9 -0
  322. package/src/invoicing/contracts/capabilities.ts +15 -0
  323. package/src/invoicing/contracts/companyLookup.ts +13 -0
  324. package/src/invoicing/contracts/delivery.ts +24 -0
  325. package/src/invoicing/contracts/facts.ts +60 -0
  326. package/src/invoicing/contracts/host.ts +85 -0
  327. package/src/invoicing/contracts/index.ts +16 -0
  328. package/src/invoicing/contracts/provider.ts +78 -0
  329. package/src/invoicing/contracts/settlement.ts +39 -0
  330. package/src/invoicing/contracts/store.ts +98 -0
  331. package/src/invoicing/index.ts +1 -0
  332. package/src/payments/stripe/index.ts +30 -0
  333. package/src/payments/tpay/index.ts +36 -0
  334. package/src/subscriptions/automaticRenewal.ts +87 -0
  335. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  336. package/src/subscriptions/contracts/commands.ts +46 -0
  337. package/src/subscriptions/contracts/dunning.ts +48 -0
  338. package/src/subscriptions/contracts/index.ts +6 -0
  339. package/src/subscriptions/contracts/messages.ts +18 -0
  340. package/src/subscriptions/contracts/renewal.ts +325 -0
  341. package/src/subscriptions/contracts/stockWait.ts +34 -0
  342. package/src/subscriptions/index.ts +24 -0
  343. package/src/subscriptions/paymentOutcomes.ts +23 -0
  344. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  345. package/src/subscriptions/postgresRetryStore.ts +174 -0
  346. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  347. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  348. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  349. package/src/subscriptions/renewalIdentity.ts +5 -0
@@ -0,0 +1,73 @@
1
+ type DeliveryKind = "parcel-locker" | "courier";
2
+ type PickupPoint = { id: string; provider: string; name: string; address: { line1: string; postalCode: string; city: string; country: string } };
3
+ export interface FulfillmentSelection {
4
+ providerRef: string | null;
5
+ carrierRef: string | null;
6
+ serviceRef: string | null;
7
+ deliveryKind: DeliveryKind;
8
+ pickupPoint: PickupPoint | null;
9
+ }
10
+ export interface FulfillmentRefusal {
11
+ reason: "not_dispatchable" | "selection_required" | "provider_mismatch" | "carrier_service_required" | "disabled" | "not_ready" | "unsupported" | "withheld" | "claim_conflict" | "contact_stale" | "stock_contention" | "local_missing" | "io_failure" | "malformed" | "untrusted" | "unsupported_event" | "stale" | "missing_correlation";
12
+ phase: "selection" | "prepare" | "claim" | "begin" | "dispatch" | "acknowledge" | "verify" | "normalize" | "apply" | "readback" | "sync";
13
+ retryable: boolean;
14
+ }
15
+ /** Frozen domain input. Contact PII stays transient; only allowlisted mapped items enter evidence. Credentials stay in adapter closures. */
16
+ export interface FulfillmentDispatchRequest {
17
+ requestKey: string;
18
+ fulfillmentRef: string;
19
+ orderRef: string;
20
+ orderNumber: string | null;
21
+ parcelRef: string | null;
22
+ contactRevision: { schemaVersion: number; source: string; revision: number };
23
+ recipient: { name: string | null; email: string | null; phone: string | null; line1: string; line2: string | null; city: string; postalCode: string; country: string; deliveryInstructions: string | null; courierInstructions: string | null };
24
+ lines: readonly { skuRef: string; title: string | null; quantity: number; productRef: string | null; packRef: string | null; lotRef?: string | null; expirationDate?: string | null }[];
25
+ selection: FulfillmentSelection;
26
+ }
27
+ /** Allowlisted durable evidence; fingerprint commits to actual adapter command bytes. */
28
+ export interface DispatchEvidence {
29
+ redactedFieldCount?: number;
30
+ commandFingerprint: string;
31
+ contactRevision: FulfillmentDispatchRequest["contactRevision"];
32
+ providerRef: string;
33
+ carrierRef: string | null;
34
+ serviceRef: string | null;
35
+ transcript?: {
36
+ itemCount: number;
37
+ items: readonly { skuRef: string; quantity: number; lotRef: string | null; expirationDate: string | null }[];
38
+ deliveryKind: DeliveryKind; pickupPointRef: string | null;
39
+ /** Count of populated reference/date fields redacted by the durable projection. */
40
+ redactedFieldCount?: number;
41
+ };
42
+ mapping?: { source: "dispatch_worker"; mode: "shadow" | "stage" | "live"; shadowMode: boolean };
43
+ }
44
+ export interface FulfillmentOperationError {
45
+ redactedFieldCount?: number;
46
+ code: string; status: number | null; retryable: boolean; mayHaveSucceeded: boolean;
47
+ /** Machine cause only; never an arbitrary exception message. */
48
+ cause?: string;
49
+ }
50
+ export type FulfillmentDispatchResult = ({ error?: FulfillmentOperationError; redactedFieldCount?: number } & (
51
+ | { kind: "accepted"; externalRef: string; evidence: DispatchEvidence; providerAttemptKey: string; labelKey: string; recovery?: { source: "persisted_acceptance"; proof: "persisted_external_ref"; noProviderPost: true } }
52
+ | { kind: "not_dispatched"; effectMayHaveSucceeded: false; refusal: FulfillmentRefusal }
53
+ | { kind: "unknown"; effectMayHaveSucceeded: true; refusal: FulfillmentRefusal }));
54
+ export type FulfillmentDispatchPrepareResult =
55
+ | { kind: "prepared"; evidence: DispatchEvidence }
56
+ | { kind: "refused"; refusal: FulfillmentRefusal };
57
+ export interface FulfillmentDispatchPort {
58
+ /**
59
+ * Without external submission, build the actual command and fingerprint its bytes
60
+ * before store claim/begin. Return only sanitized evidence; keep the raw command
61
+ * transient inside the adapter. Refuse not_dispatchable or the exact selection
62
+ * refusal when preparation cannot proceed. A contact_stale remap requires a new
63
+ * prepare of the rebuilt request/command before another claim/begin.
64
+ */
65
+ prepare(request: FulfillmentDispatchRequest): Promise<FulfillmentDispatchPrepareResult>;
66
+ /**
67
+ * Send exactly the prepared command identified by this evidence. Never silently
68
+ * rebuild a different command; absent/mismatched preparation refuses before I/O.
69
+ * Absent/mismatched preparation: not_dispatchable, phase dispatch, retryable false.
70
+ * Unknown submission outcomes require readback, never blind re-submission.
71
+ */
72
+ dispatch(request: FulfillmentDispatchRequest, evidence: DispatchEvidence): Promise<FulfillmentDispatchResult>;
73
+ }
@@ -0,0 +1,18 @@
1
+ export type { FulfillmentOperationError, DispatchEvidence, FulfillmentDispatchPrepareResult, FulfillmentSelection, FulfillmentDispatchRequest, FulfillmentDispatchResult, FulfillmentDispatchPort, FulfillmentRefusal } from "./dispatch.js";
2
+ export type { LabelDocument, FulfillmentLabelPort } from "./label.js";
3
+ /** G17 local reads/ingress persistence are inline runtime worker inputs; this provider envelope is unchanged. */
4
+ export type { FulfillmentObservation, FulfillmentObservationCodecs, FulfillmentObservationPort, FulfillmentTrackingReference } from "./observation.js";
5
+ export type { FulfillmentReadbackResult, FulfillmentReadbackPort, FulfillmentReconciliationObservation, FulfillmentObservationPagePort } from "./readback.js";
6
+ export type { SyncCursor, StockSnapshot, StockMovement, LocalStock, FulfillmentStockSyncProvider, FulfillmentStockSyncPort, FulfillmentProductSyncProvider, FulfillmentProductSyncPort } from "./sync.js";
7
+ export type { FulfillmentCapabilityDescriptor } from "./capabilities.js";
8
+ export type { DispatchRecord, FulfillmentStorePort, FulfillmentPaidHandoffPort } from "./store.js";
9
+ export type { DeliveryAlignmentPort } from "./alignment.js";
10
+ export type { FulfillmentAdapterConformanceSubject, FulfillmentAdapterConformanceEvidence } from "./conformance.js";
11
+ export type { FulfillmentNativeUnsupported } from './nativeUnsupported.js';
12
+ export type { FulfillmentReplacementPort, FulfillmentReplacementRequest, FulfillmentReplacementResult } from './replacement.js';
13
+ export type { FulfillmentOperatorMutationPort, FulfillmentOperatorMutationResult } from './operator.js';
14
+ export type { CustomerDeliveryAlignment, FulfillmentCustomerAlignmentRowsReader } from './customerAlignment.js';
15
+ export type { FulfillmentCommerceReadPort, FulfillmentCommerceReadPolicy, FulfillmentOrderStatus, FulfillmentPaymentStatus, FulfillmentCommerceOrder, FulfillmentOrdersListRequest, FulfillmentOrdersListResponse, FulfillmentOrderDetailRequest, FulfillmentOrderDetailResponse, FulfillmentShipmentsOverviewPort, FulfillmentShipmentsOverviewRequest, FulfillmentShipmentsOverviewResponse } from './read.js';
16
+ export type { FulfillmentProgressInput, FulfillmentProgressPresentation } from './read.js';
17
+ export type { FulfillmentServicesOptions, FulfillmentServices } from './bindings.js';
18
+ export type { CanonicalLocalFulfillmentStatus, CustomerFulfillmentStep } from "@openlup/core/fulfillment";
@@ -0,0 +1,9 @@
1
+ import type { FulfillmentDispatchRequest, FulfillmentRefusal } from "./dispatch.js";
2
+ /** Durable storage pointer. No signed URL or credential belongs here. */
3
+ export interface LabelDocument { objectRef: string; mediaType: "application/pdf"; externalRef: string | null }
4
+ export interface FulfillmentLabelPort {
5
+ create(input: FulfillmentDispatchRequest): Promise<{ kind: "created"; parcelRef: string; document: LabelDocument | null } | { kind: "refused"; refusal: FulfillmentRefusal }>;
6
+ /** Transient host response only; never part of a store record. */
7
+ read(document: LabelDocument): Promise<{ kind: "available"; url: string; expiresAt: string | null } | { kind: "refused"; refusal: FulfillmentRefusal }>;
8
+ void(input: { parcelRef: string; document: LabelDocument | null }): Promise<{ providerDeletion: "confirmed" | "unconfirmed"; labelDeleted: boolean; canProceed: boolean; refusal?: FulfillmentRefusal }>;
9
+ }
@@ -0,0 +1,6 @@
1
+ /** Shared refusal for host-owned operations without native parity. No partial write is permitted. */
2
+ export interface FulfillmentNativeUnsupported {
3
+ readonly kind: 'native_unsupported';
4
+ readonly reason: 'host_binding_required';
5
+ readonly retryable: false;
6
+ }
@@ -0,0 +1,26 @@
1
+ import type { NormalizedFulfillmentFact } from "@openlup/core/fulfillment";
2
+ import type { CanonicalLocalFulfillmentStatus } from "./index.js";
3
+ import type { FulfillmentRefusal } from "./dispatch.js";
4
+ /** Explicit adopter policy, forwarded through each durable observation projection. */
5
+ export interface FulfillmentObservationCodecs {
6
+ approveReference?(value: string): boolean;
7
+ /** Overrides the query/fragment default; HTTPS, no credentials and URL approval still apply. */
8
+ trackingUrl?(url: string): string | null;
9
+ }
10
+ export interface FulfillmentTrackingReference { trackingNumber: string; /** Normalized carrier kind, never a raw provider code. */ carrierRef: string | null; serviceRef: string | null; publicUrl: string | null }
11
+ /** Durable normalized envelope. eventKey, not the receivedAt-bearing fact fingerprint, deduplicates retries. */
12
+ export interface FulfillmentObservation {
13
+ fact: NormalizedFulfillmentFact<CanonicalLocalFulfillmentStatus>;
14
+ eventKind: "shipment.accepted" | "order.processing_started" | "order.picked" | "order.shipped" | "order.delivered" | "parcel.exception" | "parcel.cancelled";
15
+ eventKey: string;
16
+ firstReceivedAt: string;
17
+ correlation: { orderRef: string | null; fulfillmentRef: string | null; externalRef: string | null; orderNumber: string | null; providerFulfillmentRef: string | null; parcelRef: string | null };
18
+ trackingRefs: readonly FulfillmentTrackingReference[];
19
+ /** Original provider status tokens; canonical status does not identify an occurrence. */
20
+ providerStatus?: { primary: string | null; subStatus: string | null };
21
+ diagnostics?: { redactedFieldCount?: number; admission?: { rejectedTrackingRefs: number; rejectedTrackingUrls: number }; refusal?: FulfillmentRefusal; stockContention?: { sqlState: "55P03"; phase: "provider_stock_consumed"; retryable: true }; annotation?: "recorded" | "failed"; recoveredPickedReceipt?: { receiptRef: string; closed: boolean } };
22
+ }
23
+ export interface FulfillmentObservationPort {
24
+ /** Authenticate untouched bytes before parsing. Inputs live only during verification. */
25
+ verifyAndNormalize(input: { rawBody: Uint8Array; headers: Readonly<Record<string, string>>; receivedAt: string }): Promise<{ kind: "verified"; observation: FulfillmentObservation } | { kind: "refused"; refusal: FulfillmentRefusal; diagnostics?: { invalidTimestamp?: true; rejectedCorrelationRefs?: number } }>;
26
+ }
@@ -0,0 +1,31 @@
1
+ import type { FulfillmentNativeUnsupported } from './nativeUnsupported.js';
2
+ /** Host transaction inputs. actorUserId and idempotencyKey are required for attribution and replay receipts. */
3
+ interface MutationInput { idempotencyKey: string; actorUserId: string; metadata?: Record<string, unknown> }
4
+ interface ParcelMutationInput extends MutationInput { fulfillmentOrderId: string }
5
+ type AdminCommerceFulfillmentCreateRequest = MutationInput & { orderId: string };
6
+ type AdminCommerceFulfillmentRecordProviderAttemptRequest = ParcelMutationInput & { providerKind: string; status: 'recorded' | 'succeeded' | 'failed'; requestPayload?: Record<string, unknown>; responsePayload?: Record<string, unknown>; error?: string | null };
7
+ type AdminCommerceFulfillmentRecordLabelRequest = ParcelMutationInput & { providerKind: string; providerTrackingId: string; labelUrl?: string | null; deliveryEstimateDays?: number | null; rawProviderPayload?: Record<string, unknown> };
8
+ type AdminCommerceFulfillmentHandOffRequest = ParcelMutationInput;
9
+ type AdminCommerceFulfillmentTrackingEventRequest = ParcelMutationInput & { status: 'in_transit' | 'delivered' | 'exception'; providerTrackingId?: string; rawEvent?: Record<string, unknown> };
10
+ type AdminCommerceFulfillmentCancelRequest = ParcelMutationInput & { reason: string };
11
+ export interface FulfillmentOperatorMutationResult { contractVersion: 'commerce.fulfillment.v0'; fulfillmentOrderId: string; orderId: string; status: 'created' | 'packed' | 'label_pending' | 'label_created' | 'handed_over' | 'in_transit' | 'delivered' | 'exception' | 'cancelled'; replayed: boolean }
12
+ export interface FulfillmentOperatorMutationPort {
13
+ createCommerceFulfillmentOrder(
14
+ request: AdminCommerceFulfillmentCreateRequest & { actorUserId: string },
15
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
16
+ recordCommerceFulfillmentProviderAttempt(
17
+ request: AdminCommerceFulfillmentRecordProviderAttemptRequest & { actorUserId: string },
18
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
19
+ recordCommerceFulfillmentLabel(
20
+ request: AdminCommerceFulfillmentRecordLabelRequest & { actorUserId: string },
21
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
22
+ handOffCommerceFulfillmentOrder(
23
+ request: AdminCommerceFulfillmentHandOffRequest & { actorUserId: string },
24
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
25
+ recordCommerceFulfillmentTrackingEvent(
26
+ request: AdminCommerceFulfillmentTrackingEventRequest & { actorUserId: string },
27
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
28
+ cancelCommerceFulfillmentOrder(
29
+ request: AdminCommerceFulfillmentCancelRequest & { actorUserId: string },
30
+ ): Promise<FulfillmentOperatorMutationResult | FulfillmentNativeUnsupported>;
31
+ }
@@ -0,0 +1,42 @@
1
+ import type { SubscriptionCycleStatus } from '@openlup/core/subscription';
2
+ import type { PaymentIntentStatus } from '@openlup/core/payment';
3
+ import type { FulfillmentNativeUnsupported } from './nativeUnsupported.js';
4
+ export type FulfillmentOrderStatus = 'draft' | 'pending_payment' | 'paid' | 'failed' | 'expired' | 'fulfillment_pending' | 'cancelled' | 'fulfilled' | 'refunded';
5
+ export type FulfillmentPaymentStatus = 'not_started' | PaymentIntentStatus;
6
+ export interface FulfillmentCommerceOrder {
7
+ id: string; orderId: string; clientId: string | null;
8
+ status: 'created' | 'packed' | 'label_pending' | 'label_created' | 'handed_over' | 'in_transit' | 'delivered' | 'exception' | 'cancelled';
9
+ providerKind: string | null; providerTrackingId: string | null;
10
+ shippingAddress: { addressId: string; clientId: string; label: string | null; line1: string; line2: string | null; city: string; postalCode: string; country: string };
11
+ lines: { id: string | null; orderItemId: string; skuId: string; sku: string; title: string | null; quantity: number; inventoryReservationIds: string[]; productSnapshot: Record<string, unknown> }[];
12
+ payment: { paymentIntentId: string; paymentStatus: FulfillmentPaymentStatus; providerPaymentId: string | null };
13
+ inventory: { status: 'not_checked' | 'reserved' | 'missing' | 'released' | 'expired' | 'consumed' | 'review_required'; reservationId: string | null; reservationStatus: 'reserved' | 'consumed' | 'released' | 'expired' | null; expiresAt: string | null; locationId: string | null; locationCode: string | null };
14
+ omsEligibility: { allowed: boolean; reason: 'order_not_paid' | 'payment_not_succeeded' | 'subscription_cycle_not_paid' | 'active_hold' | 'missing_shipping_address' | 'inventory_review' | 'risk_review' | null };
15
+ latestOperation: { id: string; type: 'created' | 'packed' | 'provider_attempt_recorded' | 'label_created' | 'handed_over' | 'tracking_event_recorded' | 'cancelled'; occurredAt: string; actorUserId: string | null; payload: Record<string, unknown> } | null;
16
+ createdAt: string; updatedAt: string;
17
+ }
18
+ export interface FulfillmentOrdersListRequest { page: number; pageSize: number; status?: FulfillmentCommerceOrder['status'] }
19
+ export interface FulfillmentOrderDetailRequest { fulfillmentOrderId?: string; orderId?: string }
20
+ export interface FulfillmentOrdersListResponse { contractVersion: 'commerce.fulfillment.v0'; orders: FulfillmentCommerceOrder[]; totalCount: number; page: number; pageSize: number }
21
+ export interface FulfillmentOrderDetailResponse { contractVersion: 'commerce.fulfillment.v0'; order: FulfillmentCommerceOrder }
22
+ /** Host-owned persisted projections. Payment statuses already come from the payments projection. */
23
+ export interface FulfillmentCommerceReadPort {
24
+ listCommerceFulfillmentOrders(request: FulfillmentOrdersListRequest): Promise<FulfillmentOrdersListResponse | FulfillmentNativeUnsupported>;
25
+ getCommerceFulfillmentOrderDetail(request: FulfillmentOrderDetailRequest): Promise<FulfillmentOrderDetailResponse | null | FulfillmentNativeUnsupported>;
26
+ }
27
+ export interface FulfillmentShipmentsOverviewRequest { shippedFilter: 'today' | '7d' | '30d' | 'all' }
28
+ /** Bucket rows are opaque host data; native shipment counts cannot replace these buckets. */
29
+ export interface FulfillmentShipmentsOverviewResponse { approved: Record<string, unknown>[]; packing: Record<string, unknown>[]; shippedToday: number; shipped: Record<string, unknown>[] }
30
+ export interface FulfillmentShipmentsOverviewPort {
31
+ getShipmentsOverview(request: FulfillmentShipmentsOverviewRequest): Promise<FulfillmentShipmentsOverviewResponse | FulfillmentNativeUnsupported>;
32
+ }
33
+ /** Existing commerce eligibility remains host-owned; the read model only supplies its facts. */
34
+ export interface FulfillmentCommerceReadPolicy {
35
+ evaluateCommerceFulfillmentEligibility(input: {
36
+ orderMode: 'one_time' | 'subscription_cycle'; orderStatus: FulfillmentOrderStatus; paymentStatus: FulfillmentPaymentStatus;
37
+ subscriptionCycleStatus: SubscriptionCycleStatus | null; activeHoldCount: number; hasShippingAddress: boolean;
38
+ inventoryStatus: FulfillmentCommerceOrder['inventory']['status'];
39
+ }): FulfillmentCommerceOrder['omsEligibility'];
40
+ }
41
+
42
+ export type { FulfillmentProgressInput, FulfillmentProgressPresentation } from "@openlup/core/fulfillment";
@@ -0,0 +1,24 @@
1
+ import type { FulfillmentObservation } from "./observation.js";
2
+ import type { FulfillmentRefusal } from "./dispatch.js";
3
+ export type FulfillmentReadbackResult =
4
+ | { kind: "found"; externalRef: string; createdAt: string | null; updatedAt: string | null; observation: FulfillmentObservation }
5
+ | { kind: "absent"; scope: { providerRef: string; requestKey: string; fulfillmentRef: string; externalRef: string | null }; proof: { kind: "authoritative_exact_lookup"; evidenceRef: `evidence:${string}`; checkedAt: string } }
6
+ | { kind: "unknown"; refusal: FulfillmentRefusal }
7
+ | { kind: "unsupported"; refusal: FulfillmentRefusal };
8
+ export interface FulfillmentReadbackPort {
9
+ /** Empty pages and missing local rows cannot certify external absence. */
10
+ read(input: { providerRef: string; requestKey: string; fulfillmentRef: string; externalRef: string | null; mode: "command" | "parcel" }): Promise<FulfillmentReadbackResult>;
11
+ }
12
+
13
+ /** Collection observation is separate from authoritative exact lookup. */
14
+ export type FulfillmentReconciliationObservation = FulfillmentReadbackResult | {
15
+ kind: "unmapped"; externalRef: string | null;
16
+ evidence: Pick<FulfillmentObservation, "correlation" | "eventKey" | "providerStatus" | "diagnostics">;
17
+ };
18
+ export interface FulfillmentObservationPagePort {
19
+ readPage(input: { page: number; size: number }): Promise<
20
+ | { kind: "page"; rows: readonly FulfillmentReconciliationObservation[] }
21
+ | { kind: "page_exhausted" }
22
+ | { kind: "failed"; refusal: FulfillmentRefusal; error?: import("./dispatch.js").FulfillmentOperationError }
23
+ >;
24
+ }
@@ -0,0 +1,15 @@
1
+ import type { FulfillmentNativeUnsupported } from './nativeUnsupported.js';
2
+ export interface FulfillmentReplacementRequest {
3
+ idempotencyKey: string; actorUserId: string; orderId: string;
4
+ reason: 'damaged' | 'lost' | 'returned_undelivered' | 'other'; metadata?: Record<string, unknown>;
5
+ }
6
+ export interface FulfillmentReplacementResult {
7
+ contractVersion: 'commerce.v0';
8
+ operation: { id: string; orderId: string; type: 'hold_created' | 'hold_released' | 'fulfillment_reviewed' | 'support_note' | 'shipping_address_updated' | 'order_marked_refunded' | 'order_cancelled_manual' | 'replacement_shipment_requested'; holdId: string | null; actorUserId: string | null; occurredAt: string; payload: Record<string, unknown> };
9
+ replacement: { fulfillmentOrderId: string; orderId: string; sequenceNo: number; replacesFulfillmentOrderId: string; reason: FulfillmentReplacementRequest['reason']; status: string };
10
+ releasedHoldIds: string[]; replayed: boolean;
11
+ }
12
+ /** Host owns atomic paid/contact/current-parcel/hold/reservation checks and actor-bound fingerprint/replay receipts. No confirmation or dispatch operation exists here. */
13
+ export interface FulfillmentReplacementPort {
14
+ requestReplacementShipment(request: FulfillmentReplacementRequest): Promise<FulfillmentReplacementResult | FulfillmentNativeUnsupported>;
15
+ }
@@ -0,0 +1,27 @@
1
+ import type { DispatchEvidence, FulfillmentDispatchRequest, FulfillmentDispatchResult, FulfillmentRefusal } from "./dispatch.js";
2
+ export interface DispatchRecord {
3
+ dispatchRef: string; requestKey: string; fulfillmentRef: string; orderRef: string; parcelRef: string | null;
4
+ externalRef: string | null; mode: "shadow" | "stage" | "live";
5
+ status: "draft" | "submitting" | "created" | "uncertain" | "failed" | "cancel_requested" | "cancelled";
6
+ evidence: DispatchEvidence; replayed: boolean;
7
+ acceptance?: Extract<FulfillmentDispatchResult, { kind: "accepted" }> | null;
8
+ }
9
+ export interface FulfillmentStorePort {
10
+ read(requestKey: string): Promise<DispatchRecord | null>;
11
+ readCandidate(fulfillmentRef: string): Promise<FulfillmentDispatchRequest | null>;
12
+ prepare(fulfillmentRef: string): Promise<{ disposition: "ready" | "withheld"; contactRevision: number | null }>;
13
+ /** Evidence comes from adapter prepare, before any external submission. */
14
+ claim(input: { requestKey: string; fulfillmentRef: string; orderRef: string; parcelRef: string | null; mode: DispatchRecord["mode"]; evidence: DispatchEvidence }): Promise<{ kind: "claimed"; record: DispatchRecord } | { kind: "refused"; refusal: FulfillmentRefusal }>;
15
+ /** G14: idempotent mapped attempt; persist only prepared, sanitized evidence. */
16
+ recordProviderAttempt(attemptKey: string, fulfillmentRef: string, preparedEvidence: DispatchEvidence, mode: DispatchRecord["mode"], status: "recorded"): Promise<{ replayed: boolean }>;
17
+ /** Bind submission to the same prepared command fingerprint recorded by claim. */
18
+ begin(input: { dispatchRef: string; commandFingerprint: string }): Promise<{ kind: "begun"; record: DispatchRecord; begun: boolean; replayed: boolean } | { kind: "refused"; refusal: FulfillmentRefusal }>;
19
+ /** Accepted rescue retains externalRef as uncertain; only acknowledge closes the local acceptance atomically. */
20
+ finalize(input: { dispatchRef: string; result: FulfillmentDispatchResult }): Promise<DispatchRecord>;
21
+ acknowledge(input: { dispatchRef: string; acceptance: Extract<FulfillmentDispatchResult, { kind: "accepted" }> }): Promise<{ acknowledged: boolean; replayed: boolean; record: DispatchRecord }>;
22
+ markStaleSubmissionsUncertain(staleBefore: string): Promise<number>;
23
+ }
24
+ export interface FulfillmentPaidHandoffPort {
25
+ ensureObligation(input: { requestKey: string; orderRef: string; paidFactRef: string }): Promise<{ fulfillmentRef: string; replayed: boolean }>;
26
+ acknowledge(input: { requestKey: string; fulfillmentRef: string }): Promise<{ acknowledged: boolean; replayed: boolean }>;
27
+ }
@@ -0,0 +1,72 @@
1
+ import type { FulfillmentRefusal } from "./dispatch.js";
2
+ /** Collection identity never masquerades as a monotonic provider revision. */
3
+ interface SyncIdentity {
4
+ providerRef: string; syncRunId: string; collectedAt: string;
5
+ sourceRevision: string | null; revisionSource: "provider" | "collection" | "unknown";
6
+ }
7
+ export interface StockSnapshot extends SyncIdentity {
8
+ skuRef: string; quantityKind: "snapshot"; totalQuantity: number; forSaleQuantity: number; reservedOrUnavailableQuantity: number;
9
+ }
10
+ export interface StockMovement extends SyncIdentity {
11
+ skuRef: string; quantityKind: "movement"; quantity: number; occurredAt: string | null;
12
+ operationType: string | null; documentRef: string | null; lotRef: string | null; expirationDate: string | null;
13
+ }
14
+ export interface SyncCursor {
15
+ status: "idle" | "running" | "succeeded" | "failed";
16
+ lastStockSyncedAt: string | null; lastMovementOccurredAt: string | null;
17
+ /** Sanitized continuation only; no credentials, URLs or arbitrary provider payload. */
18
+ continuation: { nextRef: string | null; startLocalDate: string | null; endLocalDate: string | null };
19
+ /** G15: successful sellable coverage gaps; legacy absent values read as []. */
20
+ coverageGapSkus?: readonly string[];
21
+ refusal: FulfillmentRefusal | null;
22
+ /** Per-entry bounded diagnostics and run evidence in the existing cursor JSON, never raw errors. */
23
+ syncRunId?: string;
24
+ counts?: { checked: number; updated: number; movements: number; rejected: number; failures: number; providerCalls: number; readBacks: number };
25
+ items?: readonly { kind?: "stock" | "movement"; itemIndex: number; skuRef: string | null; status: "recorded" | "failed" | "rejected"; reason?: string;
26
+ activeReserved?: number; sellableNow?: number | null; coverageGapQuantity?: number }[];
27
+ alerts?: readonly { skuRef: string; thresholdKind: Exclude<Threshold, "forecast">; severity: "info" | "warning" | "critical";
28
+ providerForSaleQuantity: number; localAvailableQuantity: number; detectedAt: string;
29
+ activeReserved?: number; coverageGapQuantity?: number; gapHours?: number; thresholdHours?: number }[];
30
+ staleSync?: { gapHours: number; thresholdHours: number; detectedAt: string };
31
+ error?: { reason: string; providerError?: { operation: "get_stock" | "get_stock_movements"; status: number | null; code: string; retryable: boolean } } | null;
32
+ }
33
+ export interface LocalStock { skuRef: string; onHand: number; reserved: number; unavailable: number; safetyStock: number }
34
+ type Threshold = "safety_stock" | "reservation_coverage" | "forecast" | "stale_sync";
35
+ type Conflict = "unknown_sku" | "ean_conflict" | "kind_quantity_conflict";
36
+ export interface FulfillmentStockSyncProvider {
37
+ /** Rejected rows carry only their input index and a closed reason, never an unsafe reference. */
38
+ getStock(): Promise<(StockSnapshot | { kind: "rejected"; itemIndex: number; reason: "invalid_sku_ref" })[]>;
39
+ getStockMovements(input?: { startLocalDate?: string; endLocalDate?: string; size?: number }): Promise<(StockMovement | { kind: "rejected"; itemIndex: number; reason: "invalid_sku_ref" })[]>;
40
+ }
41
+ export interface FulfillmentStockSyncPort {
42
+ readCursor(): Promise<SyncCursor | null>;
43
+ /** G16: unaggregated lines at the worker clock; quantity retains the four local balances. */
44
+ readLocalInventoryStock(requestedAt: string): Promise<readonly {
45
+ skuRef: string; quantity: Pick<LocalStock, "onHand" | "reserved" | "unavailable" | "safetyStock">;
46
+ fulfillable: boolean; locationStatus: "active" | "inactive";
47
+ lotStatus: "available" | "quarantined" | "expired" | "recalled" | null; expiresAt: string | null;
48
+ }[]>;
49
+ readSkuInventoryClasses(skus: string[]): Promise<Map<string, "sellable" | "packaging" | null>>;
50
+ readActiveReservations(skus: string[], requestedAt: string): Promise<Map<string, number>>;
51
+ recordCursor(input: SyncCursor): Promise<void>;
52
+ recordStockSnapshot(input: { requestKey: string; snapshot: StockSnapshot; local: LocalStock; mismatchKind: "none"; inventoryClass: "sellable" | "packaging" | null }): Promise<{ replayed: boolean; readBack: boolean }>;
53
+ recordProviderStockCurrent(input: { requestKey: string; snapshot: StockSnapshot; inventoryClass: "sellable" | "packaging" | null; lastSyncedAt: string; staleAfter: string }): Promise<{ replayed: boolean; readBack: boolean }>;
54
+ recordMovement(input: { requestKey: string; movement: StockMovement }): Promise<{ replayed: boolean; readBack: boolean }>;
55
+ recordLowStockEvidence(input: { requestKey: string; skuRef: string; thresholdKind: Exclude<Threshold, "forecast">; severity: "info" | "warning" | "critical"; providerForSaleQuantity: number; localAvailableQuantity: number; syncRunId: string }): Promise<{ replayed: boolean }>;
56
+ resolveLowStockEvidence(input: { skuRef: string; activeThresholdKinds: Threshold[]; syncRunId: string }): Promise<{ resolved: number }>;
57
+ }
58
+ interface ProductPack { ean: string; quantity: number }
59
+ export interface FulfillmentProductSyncProvider {
60
+ /** null means skipped (no private push metadata), never a newly created local SKU. */
61
+ ensureProduct(sku: string): Promise<{ replayed: boolean } | null>;
62
+ getProductPacks(sku: string): Promise<ProductPack[]>;
63
+ addProductPack(sku: string, pack: ProductPack): Promise<void>;
64
+ listStockedSkus(): Promise<string[]>;
65
+ }
66
+ export interface FulfillmentProductSyncPort {
67
+ listLocalProductPacks(): Promise<{ sku: string; packs: (ProductPack & { kind: "unit" | "collective" })[] }[]>;
68
+ markPackSynced(input: { sku: string; ean: string }): Promise<void>;
69
+ upsertPulledPack(input: { sku: string; ean: string; quantity: number; kind: "unit" | "collective" }): Promise<{ conflict: "none" | "local_locked_mismatch" | "ean_owned_by_other_sku" | "sku_not_found"; replayed: boolean }>;
70
+ recordReconciliationEvidence(input: { requestKey: string; sku: string; ean: string | null; conflictKind: Conflict; providerQuantity: number; identity: SyncIdentity; refusal: FulfillmentRefusal | null }): Promise<{ replayed: boolean }>;
71
+ resolveReconciliationEvidence(input: { sku: string; activeConflictKinds: Conflict[]; syncRunId: string }): Promise<{ resolved: number }>;
72
+ }
@@ -0,0 +1,2 @@
1
+ export { parcelSequenceNo, compareParcelPrecedence, currentParcel, currentParcelFor, currentParcelPerOrder, currentParcelFirst, belongsToParcel, rowsForParcel } from "@openlup/core/fulfillment";
2
+ export type { FulfillmentParcelRow, ParcelAttributedRow } from "@openlup/core/fulfillment";
@@ -0,0 +1,60 @@
1
+ import type { CustomerDeliveryAlignment, FulfillmentCustomerAlignmentRowsReader } from './contracts/customerAlignment.js';
2
+ import type { FulfillmentNativeUnsupported } from './contracts/nativeUnsupported.js';
3
+ import { isNativeUnsupported } from './handlerSupport.js';
4
+
5
+ export type DeliveryAlignmentRow = Record<string, unknown>;
6
+ export type DeliveryAlignmentRowsReader = FulfillmentCustomerAlignmentRowsReader;
7
+
8
+ /**
9
+ * Reads only customer-safe current cases. An aligned case remains current while
10
+ * its aligned schedule is still the subscription's next schedule; once a paid
11
+ * cycle advances `next_cycle_at`, the historical ledger row disappears from UI.
12
+ */
13
+ export async function readFulfillmentDelay(
14
+ readRows: DeliveryAlignmentRowsReader | undefined,
15
+ nextCycleAtBySubscription: ReadonlyMap<string, string | null>,
16
+ ): Promise<Map<string, CustomerDeliveryAlignment> | FulfillmentNativeUnsupported> {
17
+ const subscriptionIds = [...nextCycleAtBySubscription.keys()].filter(Boolean);
18
+ if (!readRows || subscriptionIds.length === 0) return new Map();
19
+ const rows = await readRows(subscriptionIds);
20
+ if (isNativeUnsupported(rows)) return rows;
21
+ return mapCurrentDeliveryAlignments(rows, nextCycleAtBySubscription);
22
+ }
23
+
24
+ export function mapCurrentDeliveryAlignments(
25
+ rows: DeliveryAlignmentRow[],
26
+ nextCycleAtBySubscription: ReadonlyMap<string, string | null>,
27
+ ): Map<string, CustomerDeliveryAlignment> {
28
+ const result = new Map<string, CustomerDeliveryAlignment>();
29
+ for (const row of rows) {
30
+ const subscriptionId = text(row.subscription_id);
31
+ if (!subscriptionId || result.has(subscriptionId)) continue;
32
+ const state = text(row.state);
33
+ if (state === "protected") {
34
+ result.set(subscriptionId, { state });
35
+ continue;
36
+ }
37
+ if (state === "aligned" && sameInstant(
38
+ nullableText(row.aligned_next_cycle_at),
39
+ nextCycleAtBySubscription.get(subscriptionId) ?? null,
40
+ )) {
41
+ result.set(subscriptionId, { state });
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+
47
+ function sameInstant(left: string | null, right: string | null): boolean {
48
+ if (!left || !right) return false;
49
+ const leftTime = new Date(left).getTime();
50
+ const rightTime = new Date(right).getTime();
51
+ return Number.isFinite(leftTime) && leftTime === rightTime;
52
+ }
53
+
54
+ function text(value: unknown): string {
55
+ return typeof value === "string" ? value : "";
56
+ }
57
+
58
+ function nullableText(value: unknown): string | null {
59
+ return typeof value === "string" && value ? value : null;
60
+ }
@@ -0,0 +1,15 @@
1
+ import type { DeliveryAlignmentPort } from './contracts/index.js';
2
+ import type { PgExecutor } from './postgresDispatchStore.js';
3
+
4
+ /** Server-authorized projection; database browser roles have no direct access. */
5
+ export async function readDeliveryAlignment(db: PgExecutor, fulfillmentRef: string): ReturnType<DeliveryAlignmentPort['read']> {
6
+ const result = await db.query(`SELECT alignment.state, alignment.aligned_next_cycle_at
7
+ FROM public.fulfillment_shipments shipment
8
+ LEFT JOIN public.subscription_delivery_alignment_cases alignment ON alignment.predecessor_order_id=shipment.order_id
9
+ WHERE shipment.id=$1::uuid
10
+ ORDER BY (alignment.state IN ('protected','manual_review')) DESC, alignment.created_at ASC LIMIT 1`, [fulfillmentRef]);
11
+ const row = result.rows[0];
12
+ const date = row?.aligned_next_cycle_at;
13
+ return { disposition: row?.state === 'aligned' ? 'aligned' : row?.state === 'protected' ? 'outstanding' : 'manual_review',
14
+ nextCycleAt: date instanceof Date ? date.toISOString() : typeof date === 'string' ? date : null };
15
+ }
@@ -0,0 +1,105 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { DispatchEvidence, DispatchRecord, FulfillmentDispatchRequest, FulfillmentDispatchResult, FulfillmentOperationError } from "./contracts/index.js";
3
+
4
+ /** Machine identities only: arbitrary exception prose, URLs and credentials are omitted. */
5
+ export function safeDiagnosticCode(value: unknown): string | null {
6
+ return reference(value) && typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_]{0,95}$/u.test(value) ? value : null;
7
+ }
8
+ export function localFailureReason(stage: string, error: unknown, privacy = durableStrings()): string {
9
+ if (!error || typeof error !== "object") return stage;
10
+ const fields = error as { code?: unknown; causeCode?: unknown; causeMessage?: unknown; message?: unknown };
11
+ const diagnostic = (value: unknown) => typeof value === "string" ? privacy.token(value, safeDiagnosticCode) : null;
12
+ const ownCode = diagnostic(fields.code), causeCode = diagnostic(fields.causeCode), causeMessage = diagnostic(fields.causeMessage);
13
+ const messagePattern = /^([A-Za-z][A-Za-z0-9_]*)(?::([A-Z0-9]{5}))?$/u;
14
+ const safeMessage = typeof fields.message === "string" ? privacy.token(fields.message, value => messagePattern.test(value) ? value : null) : null;
15
+ const message = safeMessage?.match(messagePattern);
16
+ const cause = causeMessage ?? (message && (message[2] || /_(?:failed|down|missing|invalid|conflict|not_found)$/u.test(message[1]!)) ? safeDiagnosticCode(message[1]) : null);
17
+ const code = causeCode ?? ownCode ?? message?.[2];
18
+ return [stage, cause, code].filter(Boolean).join(":");
19
+ }
20
+ export function operationError(error: Omit<FulfillmentOperationError, "cause"> & { cause?: unknown }): FulfillmentOperationError {
21
+ const privacy = durableStrings(error.redactedFieldCount);
22
+ const code = privacy.token(error.code, safeDiagnosticCode) ?? "operation_failed";
23
+ const cause = typeof error.cause === "string" ? privacy.token(error.cause,
24
+ value => /^ack(?::[A-Za-z0-9_]{1,96}){0,2}$/u.test(value) ? value.slice(0,180) : null) : null;
25
+ return { code, status: Number.isInteger(error.status) && error.status! >= 100 && error.status! <= 599 ? error.status : null,
26
+ retryable: error.retryable === true, mayHaveSucceeded: error.mayHaveSucceeded === true,
27
+ ...(cause ? { cause } : {}), ...(privacy.redactedFieldCount ? { redactedFieldCount: privacy.redactedFieldCount } : {}) };
28
+ }
29
+ // The fulfillment opaque-ID rule; never accept free-form metadata as a reference.
30
+ export const reference = (value: unknown): string | null => typeof value === "string"
31
+ && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value) ? value : null;
32
+ /** Opaque by default; an explicit allowlist can admit wider incumbent references. */
33
+ export function admittedReference(value: string | null | undefined, approve?: (value: string) => boolean): string | null {
34
+ if (typeof value !== "string" || /[\p{Cc}\p{Cf}]/u.test(value)) return null;
35
+ const admitted = approve ? approve(value) : reference(value) !== null;
36
+ const text = value.trim();
37
+ return admitted && text && text.length <= 256 && !/[@]|[a-z][a-z0-9+.-]*:\/\//iu.test(text) ? text : null;
38
+ }
39
+ const isoDate = (value: string | null | undefined): string | null => typeof value === "string"
40
+ && /^\d{4}-\d\d-\d\d$/u.test(value) && Number.isFinite(Date.parse(value))
41
+ && new Date(value).toISOString().slice(0, 10) === value ? value : null;
42
+ /** One counted sanitizer for durable projections; codecs are explicit host policy. */
43
+ export function durableStrings(priorCount = 0) {
44
+ let count = Number.isSafeInteger(priorCount) && priorCount > 0 ? priorCount : 0;
45
+ return {
46
+ token(value: string | null | undefined, codec: (value: string) => string | null = reference) {
47
+ if (value == null) return null;
48
+ const safe = codec(value);
49
+ if (safe === null) count += 1;
50
+ return safe;
51
+ },
52
+ get redactedFieldCount() { return count; },
53
+ };
54
+ }
55
+ /** Copy only the durable allowlist, including when an adapter object has extra runtime properties. */
56
+ export function dispatchEvidence(evidence: DispatchEvidence): DispatchEvidence {
57
+ const privacy = durableStrings(evidence.redactedFieldCount ?? evidence.transcript?.redactedFieldCount);
58
+ const sanitize = privacy.token;
59
+ const commandFingerprint = evidence.commandFingerprint;
60
+ const source = evidence.contactRevision.source;
61
+ const providerRef = sanitize(evidence.providerRef) ?? "redacted", carrierRef = sanitize(evidence.carrierRef), serviceRef = sanitize(evidence.serviceRef);
62
+ const mode = evidence.mapping ? (sanitize(evidence.mapping.mode) ?? "shadow") as NonNullable<DispatchEvidence["mapping"]>["mode"] : null;
63
+ const transcript = evidence.transcript ? {
64
+ itemCount: evidence.transcript.items.length,
65
+ items: evidence.transcript.items.map(item => ({ skuRef: sanitize(item.skuRef) ?? "redacted", quantity: Number.isSafeInteger(item.quantity) && item.quantity > 0 ? item.quantity : 0,
66
+ lotRef: sanitize(item.lotRef), expirationDate: sanitize(item.expirationDate, isoDate) })),
67
+ deliveryKind: evidence.transcript.deliveryKind === "parcel-locker" ? "parcel-locker" as const : "courier" as const,
68
+ pickupPointRef: sanitize(evidence.transcript.pickupPointRef),
69
+ ...(privacy.redactedFieldCount > 0 ? { redactedFieldCount: privacy.redactedFieldCount } : {}),
70
+ } : null;
71
+ return {
72
+ commandFingerprint,
73
+ contactRevision: { schemaVersion: evidence.contactRevision.schemaVersion, source, revision: evidence.contactRevision.revision },
74
+ providerRef, carrierRef, serviceRef,
75
+ ...(transcript ? { transcript } : privacy.redactedFieldCount ? { redactedFieldCount: privacy.redactedFieldCount } : {}),
76
+ ...(evidence.mapping ? { mapping: { source: "dispatch_worker" as const, mode: mode!, shadowMode: evidence.mapping.mode === "shadow" } } : {}),
77
+ };
78
+ }
79
+ /** Shadow has a local command identity; it never invokes a provider or resolves credentials. */
80
+ export function shadowDispatchEvidence(request: FulfillmentDispatchRequest): DispatchEvidence {
81
+ if (!request.selection.providerRef || !request.lines.length || request.lines.some(line => !Number.isSafeInteger(line.quantity) || line.quantity <= 0)) throw new Error("dispatch_shadow_command_invalid");
82
+ return dispatchEvidence({ commandFingerprint: createHash("sha256").update(JSON.stringify(request)).digest("hex"),
83
+ contactRevision: request.contactRevision, providerRef: request.selection.providerRef,
84
+ carrierRef: request.selection.carrierRef, serviceRef: request.selection.serviceRef,
85
+ transcript: { itemCount: request.lines.length, items: request.lines.map(line => ({ skuRef: line.skuRef, quantity: line.quantity,
86
+ lotRef: line.lotRef ?? null, expirationDate: line.expirationDate ?? null })),
87
+ deliveryKind: request.selection.deliveryKind, pickupPointRef: request.selection.pickupPoint?.id ?? null } });
88
+ }
89
+ export function dispatchResult(result: FulfillmentDispatchResult): FulfillmentDispatchResult {
90
+ const privacy = durableStrings(result.redactedFieldCount);
91
+ const externalRef = result.kind === "accepted" ? privacy.token(result.externalRef) ?? "redacted" : null;
92
+ const refusal = result.kind !== "accepted" ? { reason: (privacy.token(result.refusal.reason) ?? "malformed") as typeof result.refusal.reason,
93
+ phase: (privacy.token(result.refusal.phase) ?? "normalize") as typeof result.refusal.phase, retryable: result.refusal.retryable } : null;
94
+ const redactions = privacy.redactedFieldCount ? { redactedFieldCount: privacy.redactedFieldCount } : {};
95
+ const error = result.error ? { error: operationError({ ...result.error, mayHaveSucceeded: result.kind !== "not_dispatched" }) } : {};
96
+ switch (result.kind) {
97
+ case "accepted": return { kind: "accepted", externalRef: externalRef!, providerAttemptKey: result.providerAttemptKey, labelKey: result.labelKey, ...redactions, evidence: dispatchEvidence(result.evidence), ...error,
98
+ ...(result.recovery ? { recovery: { source: "persisted_acceptance", proof: "persisted_external_ref", noProviderPost: true } } : {}) };
99
+ case "not_dispatched": return { kind: "not_dispatched", effectMayHaveSucceeded: false, refusal: refusal!, ...redactions, ...error };
100
+ case "unknown": return { kind: "unknown", effectMayHaveSucceeded: true, refusal: refusal!, ...redactions, ...error };
101
+ }
102
+ }
103
+ export function acceptedProviderProof(record: DispatchRecord | null): record is DispatchRecord & { externalRef: string } {
104
+ return Boolean(record?.externalRef && (record.status === "created" || record.status === "uncertain"));
105
+ }