@openlup/platform 0.22.0 → 0.24.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 (241) hide show
  1. package/CHANGELOG.md +440 -158
  2. package/README.md +141 -589
  3. package/dist/fulfillment/adapterConformance.js +1 -1
  4. package/dist/fulfillment/bindings.js +1 -1
  5. package/dist/fulfillment/contracts/conformance.d.ts +3 -1
  6. package/dist/fulfillment/contracts/dispatch.d.ts +45 -3
  7. package/dist/fulfillment/contracts/index.d.ts +3 -3
  8. package/dist/fulfillment/contracts/observation.d.ts +21 -1
  9. package/dist/fulfillment/contracts/readback.d.ts +21 -0
  10. package/dist/fulfillment/contracts/store.d.ts +3 -0
  11. package/dist/fulfillment/contracts/sync.d.ts +58 -2
  12. package/dist/fulfillment/dispatchProjection.d.ts +20 -1
  13. package/dist/fulfillment/dispatchProjection.js +97 -6
  14. package/dist/fulfillment/index.d.ts +3 -2
  15. package/dist/fulfillment/index.js +2 -2
  16. package/dist/fulfillment/migrations.d.ts +5 -0
  17. package/dist/fulfillment/migrations.js +1 -0
  18. package/dist/fulfillment/observationProjection.d.ts +24 -3
  19. package/dist/fulfillment/observationProjection.js +65 -12
  20. package/dist/fulfillment/omnipack/client.d.ts +3 -1
  21. package/dist/fulfillment/omnipack/client.js +31 -13
  22. package/dist/fulfillment/omnipack/index.d.ts +4 -1
  23. package/dist/fulfillment/omnipack/index.js +107 -45
  24. package/dist/fulfillment/omnipack/mappers.d.ts +7 -1
  25. package/dist/fulfillment/omnipack/mappers.js +24 -2
  26. package/dist/fulfillment/omnipack/observation.d.ts +11 -4
  27. package/dist/fulfillment/omnipack/observation.js +44 -29
  28. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  29. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  30. package/dist/fulfillment/postgresDispatchStore.js +1 -1
  31. package/dist/fulfillment/postgresShipmentSpine.d.ts +8 -1
  32. package/dist/fulfillment/postgresShipmentSpine.js +17 -3
  33. package/dist/fulfillment/postgresStockSyncPort.js +6 -3
  34. package/dist/fulfillment/providerDispatchAcceptance.d.ts +1 -0
  35. package/dist/fulfillment/providerDispatchAcceptance.js +8 -2
  36. package/dist/fulfillment/providerDispatchWorker.d.ts +51 -0
  37. package/dist/fulfillment/providerDispatchWorker.js +65 -20
  38. package/dist/fulfillment/providerReconciliationWorker.d.ts +27 -19
  39. package/dist/fulfillment/providerReconciliationWorker.js +75 -19
  40. package/dist/fulfillment/providerStockSyncWorker.d.ts +5 -1
  41. package/dist/fulfillment/providerStockSyncWorker.js +229 -120
  42. package/dist/fulfillment/providerTrackingReferences.d.ts +3 -2
  43. package/dist/fulfillment/verifiedIngress.d.ts +38 -11
  44. package/dist/fulfillment/verifiedIngress.js +36 -8
  45. package/dist/invoicing/accountingCanonicalProviderLines.d.ts +5 -0
  46. package/dist/invoicing/accountingCanonicalProviderLines.js +104 -0
  47. package/dist/invoicing/accountingLineAllocation.d.ts +2 -0
  48. package/dist/invoicing/accountingLineAllocation.js +69 -0
  49. package/dist/invoicing/adapterConformance.d.ts +42 -0
  50. package/dist/invoicing/adapterConformance.js +296 -0
  51. package/dist/invoicing/artifacts.d.ts +7 -0
  52. package/dist/invoicing/artifacts.js +20 -0
  53. package/dist/invoicing/bindings.d.ts +46 -0
  54. package/dist/invoicing/bindings.js +69 -0
  55. package/dist/invoicing/capabilities.d.ts +11 -0
  56. package/dist/invoicing/capabilities.js +10 -0
  57. package/dist/invoicing/channelPolicy.d.ts +28 -0
  58. package/dist/invoicing/channelPolicy.js +27 -0
  59. package/dist/invoicing/continuity.d.ts +12 -0
  60. package/dist/invoicing/continuity.js +18 -0
  61. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  62. package/dist/invoicing/contracts/capabilities.js +1 -0
  63. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  64. package/dist/invoicing/contracts/companyLookup.js +1 -0
  65. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  66. package/dist/invoicing/contracts/delivery.js +1 -0
  67. package/dist/invoicing/contracts/facts.d.ts +110 -0
  68. package/dist/invoicing/contracts/facts.js +1 -0
  69. package/dist/invoicing/contracts/host.d.ts +245 -0
  70. package/dist/invoicing/contracts/host.js +1 -0
  71. package/dist/invoicing/contracts/index.d.ts +16 -0
  72. package/dist/invoicing/contracts/index.js +1 -0
  73. package/dist/invoicing/contracts/provider.d.ts +126 -0
  74. package/dist/invoicing/contracts/provider.js +1 -0
  75. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  76. package/dist/invoicing/contracts/settlement.js +1 -0
  77. package/dist/invoicing/contracts/store.d.ts +277 -0
  78. package/dist/invoicing/contracts/store.js +1 -0
  79. package/dist/invoicing/contribution.d.ts +21 -0
  80. package/dist/invoicing/contribution.js +19 -0
  81. package/dist/invoicing/correction.d.ts +42 -0
  82. package/dist/invoicing/correction.js +44 -0
  83. package/dist/invoicing/delivery.d.ts +85 -0
  84. package/dist/invoicing/delivery.js +79 -0
  85. package/dist/invoicing/documentHistory.d.ts +50 -0
  86. package/dist/invoicing/documentHistory.js +213 -0
  87. package/dist/invoicing/documentSync.d.ts +24 -0
  88. package/dist/invoicing/documentSync.js +26 -0
  89. package/dist/invoicing/dutyCollector.d.ts +15 -0
  90. package/dist/invoicing/dutyCollector.js +35 -0
  91. package/dist/invoicing/fulfillmentTrigger.d.ts +56 -0
  92. package/dist/invoicing/fulfillmentTrigger.js +90 -0
  93. package/dist/invoicing/handoff.d.ts +37 -0
  94. package/dist/invoicing/handoff.js +62 -0
  95. package/dist/invoicing/index.d.ts +39 -0
  96. package/dist/invoicing/index.js +37 -0
  97. package/dist/invoicing/issue.d.ts +39 -0
  98. package/dist/invoicing/issue.js +55 -0
  99. package/dist/invoicing/migrations.d.ts +48 -0
  100. package/dist/invoicing/migrations.js +25 -0
  101. package/dist/invoicing/nativeUnsupported.d.ts +9 -0
  102. package/dist/invoicing/nativeUnsupported.js +22 -0
  103. package/dist/invoicing/paymentReadback.d.ts +20 -0
  104. package/dist/invoicing/paymentReadback.js +47 -0
  105. package/dist/invoicing/postgresInvoiceStore.d.ts +55 -0
  106. package/dist/invoicing/postgresInvoiceStore.js +344 -0
  107. package/dist/invoicing/readModel.d.ts +74 -0
  108. package/dist/invoicing/readModel.js +90 -0
  109. package/dist/invoicing/readback.d.ts +4 -0
  110. package/dist/invoicing/readback.js +30 -0
  111. package/dist/invoicing/reversal.d.ts +37 -0
  112. package/dist/invoicing/reversal.js +28 -0
  113. package/dist/invoicing/sellerRevision.d.ts +19 -0
  114. package/dist/invoicing/sellerRevision.js +14 -0
  115. package/dist/invoicing/settlementSync.d.ts +18 -0
  116. package/dist/invoicing/settlementSync.js +32 -0
  117. package/dist/invoicing/snapshot.d.ts +52 -0
  118. package/dist/invoicing/snapshot.js +141 -0
  119. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  120. package/dist/subscriptions/automaticRenewal.js +81 -0
  121. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  122. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  123. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  124. package/dist/subscriptions/contracts/commands.js +1 -0
  125. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  126. package/dist/subscriptions/contracts/dunning.js +1 -0
  127. package/dist/subscriptions/contracts/index.d.ts +6 -0
  128. package/dist/subscriptions/contracts/index.js +1 -0
  129. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  130. package/dist/subscriptions/contracts/messages.js +1 -0
  131. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  132. package/dist/subscriptions/contracts/renewal.js +1 -0
  133. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  134. package/dist/subscriptions/contracts/stockWait.js +1 -0
  135. package/dist/subscriptions/index.d.ts +39 -0
  136. package/dist/subscriptions/index.js +17 -0
  137. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  138. package/dist/subscriptions/paymentOutcomes.js +27 -0
  139. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  140. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  141. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  142. package/dist/subscriptions/postgresRetryStore.js +142 -0
  143. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  144. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  145. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  146. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  147. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  148. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  149. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  150. package/dist/subscriptions/renewalIdentity.js +4 -0
  151. package/package.json +23 -3
  152. package/release-gates.json +74 -7
  153. package/sql/20260809210000_accounting_document_rail.sql +889 -0
  154. package/sql/20260811150000_accounting_payout_rail.sql +333 -0
  155. package/sql/20260812130000_accounting_document_lifecycle_rail.sql +460 -0
  156. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  157. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  158. package/sql/20261022190000_invoicing_durable_duties.sql +494 -0
  159. package/sql/20261022191000_invoicing_document_effects.sql +249 -0
  160. package/sql/20261022192000_invoicing_payment_decoupling.sql +144 -0
  161. package/sql/20261022195000_invoicing_seller_revision.sql +65 -0
  162. package/src/fulfillment/adapterConformance.ts +1 -1
  163. package/src/fulfillment/bindings.ts +1 -1
  164. package/src/fulfillment/contracts/conformance.ts +3 -1
  165. package/src/fulfillment/contracts/dispatch.ts +24 -5
  166. package/src/fulfillment/contracts/index.ts +3 -3
  167. package/src/fulfillment/contracts/observation.ts +11 -3
  168. package/src/fulfillment/contracts/readback.ts +13 -0
  169. package/src/fulfillment/contracts/store.ts +1 -0
  170. package/src/fulfillment/contracts/sync.ts +13 -2
  171. package/src/fulfillment/dispatchProjection.ts +94 -7
  172. package/src/fulfillment/index.ts +3 -2
  173. package/src/fulfillment/migrations.ts +1 -0
  174. package/src/fulfillment/observationProjection.ts +64 -13
  175. package/src/fulfillment/omnipack/client.ts +32 -13
  176. package/src/fulfillment/omnipack/index.ts +99 -39
  177. package/src/fulfillment/omnipack/mappers.ts +23 -2
  178. package/src/fulfillment/omnipack/observation.ts +45 -27
  179. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  180. package/src/fulfillment/postgresDispatchStore.ts +1 -1
  181. package/src/fulfillment/postgresShipmentSpine.ts +21 -4
  182. package/src/fulfillment/postgresStockSyncPort.ts +6 -3
  183. package/src/fulfillment/providerDispatchAcceptance.ts +9 -3
  184. package/src/fulfillment/providerDispatchWorker.ts +61 -20
  185. package/src/fulfillment/providerReconciliationWorker.ts +81 -25
  186. package/src/fulfillment/providerStockSyncWorker.ts +222 -126
  187. package/src/fulfillment/providerTrackingReferences.ts +3 -2
  188. package/src/fulfillment/verifiedIngress.ts +44 -11
  189. package/src/invoicing/accountingCanonicalProviderLines.ts +134 -0
  190. package/src/invoicing/accountingLineAllocation.ts +69 -0
  191. package/src/invoicing/adapterConformance.ts +249 -0
  192. package/src/invoicing/artifacts.ts +19 -0
  193. package/src/invoicing/bindings.ts +80 -0
  194. package/src/invoicing/capabilities.ts +9 -0
  195. package/src/invoicing/channelPolicy.ts +50 -0
  196. package/src/invoicing/continuity.ts +15 -0
  197. package/src/invoicing/contracts/capabilities.ts +15 -0
  198. package/src/invoicing/contracts/companyLookup.ts +13 -0
  199. package/src/invoicing/contracts/delivery.ts +24 -0
  200. package/src/invoicing/contracts/facts.ts +71 -0
  201. package/src/invoicing/contracts/host.ts +84 -0
  202. package/src/invoicing/contracts/index.ts +16 -0
  203. package/src/invoicing/contracts/provider.ts +78 -0
  204. package/src/invoicing/contracts/settlement.ts +39 -0
  205. package/src/invoicing/contracts/store.ts +102 -0
  206. package/src/invoicing/contribution.ts +28 -0
  207. package/src/invoicing/correction.ts +56 -0
  208. package/src/invoicing/delivery.ts +122 -0
  209. package/src/invoicing/documentHistory.ts +301 -0
  210. package/src/invoicing/documentSync.ts +51 -0
  211. package/src/invoicing/dutyCollector.ts +31 -0
  212. package/src/invoicing/fulfillmentTrigger.ts +144 -0
  213. package/src/invoicing/handoff.ts +71 -0
  214. package/src/invoicing/index.ts +39 -0
  215. package/src/invoicing/issue.ts +64 -0
  216. package/src/invoicing/migrations.ts +25 -0
  217. package/src/invoicing/nativeUnsupported.ts +24 -0
  218. package/src/invoicing/paymentReadback.ts +70 -0
  219. package/src/invoicing/postgresInvoiceStore.ts +334 -0
  220. package/src/invoicing/readModel.ts +80 -0
  221. package/src/invoicing/readback.ts +26 -0
  222. package/src/invoicing/reversal.ts +34 -0
  223. package/src/invoicing/sellerRevision.ts +20 -0
  224. package/src/invoicing/settlementSync.ts +37 -0
  225. package/src/invoicing/snapshot.ts +192 -0
  226. package/src/subscriptions/automaticRenewal.ts +87 -0
  227. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  228. package/src/subscriptions/contracts/commands.ts +46 -0
  229. package/src/subscriptions/contracts/dunning.ts +48 -0
  230. package/src/subscriptions/contracts/index.ts +6 -0
  231. package/src/subscriptions/contracts/messages.ts +18 -0
  232. package/src/subscriptions/contracts/renewal.ts +325 -0
  233. package/src/subscriptions/contracts/stockWait.ts +34 -0
  234. package/src/subscriptions/index.ts +24 -0
  235. package/src/subscriptions/paymentOutcomes.ts +23 -0
  236. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  237. package/src/subscriptions/postgresRetryStore.ts +174 -0
  238. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  239. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  240. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  241. package/src/subscriptions/renewalIdentity.ts +5 -0
@@ -0,0 +1,66 @@
1
+ import type { InvoiceProof, InvoiceUnsupported } from "./provider.js";
2
+ /** One immutable balance item, never unique by batch+payment alone.
3
+ * Carrier: accountingControlContracts settlement + payout balance transaction ID.
4
+ * Null links and bankReceivedAt are facts; settlement is not an invoice gate.
5
+ * @consumer Host payout item recorder.
6
+ */
7
+ export interface InvoiceSettlementItem {
8
+ readonly requestKey: string;
9
+ readonly providerRef: string;
10
+ readonly batchRef: string;
11
+ readonly itemRef: string;
12
+ readonly providerPaymentRef: string;
13
+ readonly paymentIntentRef: string | null;
14
+ readonly paymentRef: string | null;
15
+ readonly invoiceRef: string | null;
16
+ readonly grossMinor: number;
17
+ readonly feeMinor: number;
18
+ readonly netMinor: number;
19
+ readonly currency: string;
20
+ readonly status: "matched" | "unmatched" | "mismatch" | "bank_pending";
21
+ readonly bankReceivedAt: string | null;
22
+ readonly proof: InvoiceProof;
23
+ }
24
+ type ItemScope = Pick<InvoiceSettlementItem, "providerRef" | "batchRef" | "itemRef" | "providerPaymentRef">;
25
+ export type InvoiceSettlementResult = {
26
+ readonly kind: "found";
27
+ readonly scope: ItemScope;
28
+ readonly paymentIntentRef: string | null;
29
+ readonly paymentRef: string | null;
30
+ readonly invoiceRef: string | null;
31
+ readonly proof: InvoiceProof;
32
+ } | {
33
+ readonly kind: "absent";
34
+ readonly scope: ItemScope;
35
+ readonly evidence: InvoiceProof & {
36
+ readonly exact: true;
37
+ readonly complete: true;
38
+ readonly scope: ItemScope;
39
+ };
40
+ } | {
41
+ readonly kind: "recorded";
42
+ readonly itemRef: string;
43
+ readonly settlementItemRef: string;
44
+ readonly replayed: boolean;
45
+ } | {
46
+ readonly kind: "conflict";
47
+ readonly itemRef: string;
48
+ readonly code: "identity_mismatch" | "money_mismatch";
49
+ } | {
50
+ readonly kind: "unknown";
51
+ readonly itemRef: string;
52
+ readonly code: "unknown";
53
+ } | InvoiceUnsupported;
54
+ /** Host recorder/resolver owns richer item identity. Unsupported before any write
55
+ * when a native batch/payment row cannot represent distinct items or nullable links.
56
+ * @consumer Host payout item recorder/resolver.
57
+ */
58
+ export interface InvoiceSettlementPort {
59
+ record(item: InvoiceSettlementItem): Promise<Exclude<InvoiceSettlementResult, {
60
+ kind: "found" | "absent";
61
+ }>>;
62
+ resolve(input: ItemScope): Promise<Exclude<InvoiceSettlementResult, {
63
+ kind: "recorded" | "conflict";
64
+ }>>;
65
+ }
66
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,277 @@
1
+ import type { InvoiceHostPorts } from "./host.js";
2
+ import type { InvoiceFacts, InvoiceClaimedFacts } from "./facts.js";
3
+ import type { InvoiceIdentity, InvoiceDocumentIdentity, InvoiceProof, InvoiceUnsupported, InvoiceWriteResult, InvoiceReadbackResult } from "./provider.js";
4
+ import type { InvoiceClearanceObservation, InvoiceArtifactResult, InvoiceDeliveryPort } from "./delivery.js";
5
+ type InvoiceDutyStatus = "owed" | "blocked" | "ready" | "processing" | "unknown" | "issued" | "voided" | "unsupported" | "faulted";
6
+ /** Native duty/lease fields map to the pinned invoicing_* fixtures. Managed hosts
7
+ * translate their outboxId/attemptCount fences; never claim transaction equality.
8
+ */
9
+ export interface InvoiceClaim {
10
+ readonly dutyId: string;
11
+ readonly documentId: string;
12
+ readonly requestId: string;
13
+ readonly token: string;
14
+ readonly generation: number;
15
+ readonly leaseUntil: string;
16
+ readonly identity: InvoiceIdentity;
17
+ readonly facts: InvoiceClaimedFacts;
18
+ readonly action: "create" | "readback";
19
+ readonly correction: {
20
+ readonly before: InvoiceFacts;
21
+ readonly reason: string;
22
+ readonly after: {
23
+ readonly totalNetMinor: 0;
24
+ readonly totalTaxMinor: 0;
25
+ readonly totalGrossMinor: 0;
26
+ };
27
+ } | null;
28
+ }
29
+ /** Exact camel-case shape returned by invoicing_duty_readback. This is not
30
+ * payment truth's accountingRequested; empty duties never assert invoice absence.
31
+ * @consumer Operator owed-duty readback.
32
+ */
33
+ export interface InvoiceDutyReadback {
34
+ readonly orderId: string;
35
+ readonly captureFaults: readonly {
36
+ readonly sourceId: string;
37
+ readonly state: "retryable" | "faulted" | "resolved";
38
+ readonly attempts: number;
39
+ readonly availableAt: string;
40
+ }[];
41
+ readonly duties: readonly {
42
+ readonly dutyId: string;
43
+ readonly parentDutyId: string | null;
44
+ readonly documentId: string | null;
45
+ readonly state: "owed" | "blocked" | "claimed" | "unknown" | "issued" | "voided" | "unsupported" | "faulted";
46
+ readonly attempts: number;
47
+ readonly factAttempts: number;
48
+ readonly diagnosticCode: "faulted" | "missing_facts" | "legacy_facts_unproved" | "retryable" | "blocked" | "unsupported" | "unknown" | "absent" | null;
49
+ readonly createdAt: string;
50
+ readonly availableAt: string;
51
+ readonly leaseUntil: string | null;
52
+ }[];
53
+ }
54
+ /** Every operation is one local transaction; commit before external I/O. Native
55
+ * success atomically saves late identity and compensation; stale catch writes nothing.
56
+ * Compatibility stores bind provider selection at construction; the structural
57
+ * native store accepts it per claim and persists the first provider on the duty.
58
+ * No implementation, executor or migration is installed by these declarations.
59
+ */
60
+ export interface InvoiceStorePort {
61
+ /** Never call inside a renewal money, dunning or dispatch transaction without
62
+ * isolation: capture failure must not propagate; the sweep recovers.
63
+ */
64
+ captureDuty(input: {
65
+ readonly requestKey: string;
66
+ readonly sourceKind: "paid" | "handoff";
67
+ readonly sourceId: string;
68
+ readonly orderId: string;
69
+ readonly occurredAt: string;
70
+ readonly evidence: {
71
+ readonly settlementIntentId: string;
72
+ readonly amountMinor: number;
73
+ readonly currency: string;
74
+ } | {
75
+ readonly shipmentId: string;
76
+ readonly handedOverAt: string;
77
+ };
78
+ }): Promise<{
79
+ readonly dutyId: string;
80
+ readonly status: InvoiceDutyStatus;
81
+ } | {
82
+ readonly kind: "conflict";
83
+ readonly code: "identity_mismatch";
84
+ } | InvoiceUnsupported>;
85
+ installFacts(input: {
86
+ readonly dutyId: string;
87
+ readonly facts: InvoiceFacts;
88
+ }): Promise<{
89
+ readonly dutyId: string;
90
+ readonly status: InvoiceDutyStatus;
91
+ readonly replayed?: boolean;
92
+ } | InvoiceUnsupported>;
93
+ /** Pinned split facts lease: sweep and prepare commit separately; finishing
94
+ * requires the same token and authoritative document/fact match. No host I/O
95
+ * under the duty lock, and unsupported never silently drops an owed duty.
96
+ */
97
+ sweep(limit: number): Promise<{
98
+ readonly collected: number;
99
+ readonly claims: readonly {
100
+ readonly dutyId: string;
101
+ readonly token: string;
102
+ }[];
103
+ } | InvoiceUnsupported>;
104
+ prepareFacts(input: {
105
+ readonly dutyId: string;
106
+ readonly token: string;
107
+ }): Promise<{
108
+ readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported" | "stale";
109
+ readonly documentId?: string;
110
+ readonly requestId?: string;
111
+ readonly code?: "retryable" | "legacy_facts_unproved";
112
+ } | InvoiceUnsupported>;
113
+ finishFacts(input: {
114
+ readonly dutyId: string;
115
+ readonly token: string;
116
+ readonly result: Omit<Exclude<Awaited<ReturnType<InvoiceStorePort["prepareFacts"]>>, InvoiceUnsupported>, "status"> & {
117
+ readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported";
118
+ };
119
+ }): Promise<{
120
+ readonly dutyId: string;
121
+ readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported" | "faulted" | "stale";
122
+ } | InvoiceUnsupported>;
123
+ readDuties(orderId: string): Promise<InvoiceDutyReadback | InvoiceUnsupported>;
124
+ /** Filter order before locking/limit; never lease unrelated orders then filter.
125
+ * P7 host composition reads the injected seller before the separate first-claim SQL wrapper;
126
+ * its revisionRef supplies p_revision, without requiring a duty from the caller.
127
+ */
128
+ claim(input: {
129
+ readonly limit: number;
130
+ readonly visibilitySeconds: number;
131
+ readonly providerRef: string;
132
+ readonly orderId?: string | null;
133
+ }): Promise<readonly InvoiceClaim[] | InvoiceUnsupported>;
134
+ preflight(input: {
135
+ readonly claim: InvoiceClaim;
136
+ readonly amountMinor: number | null;
137
+ readonly currency: string | null;
138
+ } & ({
139
+ readonly status: "succeeded" | "failed" | "pending" | "unknown" | "unavailable";
140
+ readonly proof: InvoiceProof | null;
141
+ } | {
142
+ readonly status: "absent";
143
+ readonly evidence: Extract<Awaited<ReturnType<InvoiceHostPorts["money"]["read"]>>, {
144
+ kind: "absent";
145
+ }>["evidence"];
146
+ })): Promise<{
147
+ readonly kind: "ready" | "blocked" | "mismatch" | "unavailable" | "stale";
148
+ } | InvoiceUnsupported>;
149
+ finish(input: {
150
+ readonly claim: InvoiceClaim;
151
+ readonly result: Extract<InvoiceWriteResult, {
152
+ kind: "issued";
153
+ }> | Extract<InvoiceReadbackResult, {
154
+ kind: "found";
155
+ }>;
156
+ }): Promise<{
157
+ readonly kind: "issued";
158
+ readonly replayed: boolean;
159
+ } | {
160
+ readonly kind: "stale";
161
+ } | InvoiceUnsupported>;
162
+ /** Retryable requires a definite refusal (effectMayHaveSucceeded:false) or a
163
+ * pre-I/O failure and phase before_send. Every other failure maps to unknown.
164
+ * Native result kinds other than stale/voided map to recorded.
165
+ */
166
+ fail(input: {
167
+ readonly claim: InvoiceClaim;
168
+ readonly retrySeconds: number;
169
+ } & ({
170
+ readonly kind: "absent";
171
+ readonly evidence: Extract<InvoiceReadbackResult, {
172
+ kind: "absent";
173
+ }>;
174
+ } | {
175
+ readonly kind: "retryable";
176
+ readonly phase: "before_send";
177
+ readonly proof: InvoiceProof | null;
178
+ } | {
179
+ readonly kind: "blocked" | "unsupported" | "unknown";
180
+ readonly proof: InvoiceProof | null;
181
+ })): Promise<{
182
+ readonly kind: "recorded" | "voided" | "stale";
183
+ } | InvoiceUnsupported>;
184
+ /** Native post-write unsupported maps to unsupported_recorded, retaining duty/replay.
185
+ * Missing means no M09 duty, never invoice absence: retain/retry the reversal
186
+ * intent and route legacy documents to the incumbent path.
187
+ */
188
+ requestReversal(input: {
189
+ readonly requestKey: string;
190
+ readonly orderId: string;
191
+ readonly reason: "order_canceled" | "order_refunded";
192
+ readonly proof: InvoiceProof;
193
+ }): Promise<{
194
+ readonly kind: "missing" | "voided" | "pending_readback" | "correction_requested";
195
+ readonly dutyId?: string;
196
+ readonly replayed?: boolean;
197
+ } | {
198
+ readonly kind: "unsupported_recorded";
199
+ readonly dutyId?: string;
200
+ readonly replayed?: boolean;
201
+ } | {
202
+ readonly kind: "conflict";
203
+ readonly code: "identity_mismatch";
204
+ } | InvoiceUnsupported>;
205
+ /** Existing local-only request remains distinct from fiscal issuance. */
206
+ requestLocalDocument(input: {
207
+ readonly requestKey: string;
208
+ readonly orderId: string;
209
+ readonly providerRef: string;
210
+ }): Promise<{
211
+ readonly kind: "requested";
212
+ readonly documentId: string;
213
+ readonly invoiceRef: string;
214
+ readonly status: string;
215
+ readonly replayed: boolean;
216
+ } | InvoiceUnsupported>;
217
+ listClearanceTargets(limit: number): Promise<readonly {
218
+ readonly documentId: string;
219
+ readonly identity: InvoiceDocumentIdentity;
220
+ readonly providerDocumentRef: string;
221
+ readonly providerDocumentNumber: string | null;
222
+ readonly state: InvoiceClearanceObservation["state"];
223
+ }[] | InvoiceUnsupported>;
224
+ /** Native maps clearance state unsupported to observation kind unsupported;
225
+ * provider_mismatch returns InvoiceUnsupported before recording anything.
226
+ */
227
+ recordObservation(input: {
228
+ readonly requestKey: string;
229
+ readonly documentId: string;
230
+ readonly eventRef: string;
231
+ readonly eventKind: "clearance" | "artifacts" | "unsupported" | "provider_mismatch";
232
+ readonly identity: InvoiceDocumentIdentity;
233
+ readonly providerDocumentRef: string;
234
+ readonly providerDocumentNumber: string | null;
235
+ readonly clearance: InvoiceClearanceObservation | null;
236
+ readonly artifacts: readonly Extract<InvoiceArtifactResult, {
237
+ kind: "found";
238
+ }>["artifact"][];
239
+ readonly observedAt: string;
240
+ readonly payloadHash: string;
241
+ readonly source: "provider_api" | "provider_webhook" | "manual_reconciliation";
242
+ }): Promise<{
243
+ readonly replayed: boolean;
244
+ } | InvoiceUnsupported>;
245
+ /** Verify M04 binding before claiming; document rows need no M09 issue duty. */
246
+ claimDeliveries(input: {
247
+ readonly limit: number;
248
+ readonly visibilitySeconds: number;
249
+ readonly requireClearance: boolean;
250
+ readonly orderId?: string | null;
251
+ }): Promise<readonly {
252
+ readonly token: string;
253
+ readonly generation: number;
254
+ readonly identity: InvoiceDocumentIdentity;
255
+ readonly providerDocumentRef: string;
256
+ readonly delivery: Omit<Parameters<InvoiceDeliveryPort["send"]>[0], "filename" | "content" | "contentType">;
257
+ }[] | InvoiceUnsupported>;
258
+ /** Native uncertain settlement passes p_receipt NULL and retains a non-null
259
+ * receiptRef hint in p_evidence; managed uses it as the provider message id.
260
+ * Managed failed_before_send uses the retrySeconds RPC argument. Native
261
+ * failed_before_send carries { phase: 'before_send', retryable, retrySeconds }
262
+ * in p_evidence: whole seconds clamp to 60..86400, absent/invalid uses 900.
263
+ * retryable:false parks at infinity, with failed status and last_error visible.
264
+ */
265
+ settleDelivery(input: {
266
+ readonly deliveryRef: string;
267
+ readonly token: string;
268
+ readonly generation: number;
269
+ readonly providerDocumentRef: string;
270
+ readonly retrySeconds: number;
271
+ readonly result: Exclude<Awaited<ReturnType<InvoiceDeliveryPort["send"]>>, InvoiceUnsupported>;
272
+ }): Promise<{
273
+ readonly kind: "recorded" | "stale";
274
+ readonly replayed: boolean;
275
+ } | InvoiceUnsupported>;
276
+ }
277
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
2
+ /** Inert descriptors for the host's existing registry and executor. No registration. */
3
+ export declare function createInvoiceContribution(input: {
4
+ version: string;
5
+ requiredSchema: RequiredSchema;
6
+ invoicing: Readonly<Record<string, ((...args: never[]) => unknown) | null>>;
7
+ handlers: Contribution["handlers"];
8
+ schedules: Contribution["schedules"];
9
+ routes: Contribution["routes"];
10
+ }): {
11
+ contributions: Contribution[];
12
+ ports: {
13
+ [k: string]: ((...args: never[]) => unknown) | null;
14
+ };
15
+ routes: {
16
+ [k: string]: (...args: never[]) => unknown;
17
+ };
18
+ triggers: {
19
+ [k: string]: (...args: never[]) => unknown;
20
+ };
21
+ };
@@ -0,0 +1,19 @@
1
+ /** Inert descriptors for the host's existing registry and executor. No registration. */
2
+ export function createInvoiceContribution(input) {
3
+ const contribution = {
4
+ manifest: {
5
+ name: "@openlup/platform", version: input.version, kind: "rail",
6
+ module: { id: "invoicing", kind: "capability", provides: ["./invoicing"], dependsOn: [] },
7
+ emits: [], handles: input.handlers.map(handler => handler.eventType), env: [],
8
+ schedules: input.schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })),
9
+ requiredPorts: Object.keys(input.invoicing), routes: input.routes.map(route => route.id), requiredSchema: input.requiredSchema,
10
+ },
11
+ handlers: input.handlers, schedules: input.schedules, routes: input.routes,
12
+ };
13
+ return {
14
+ contributions: [contribution],
15
+ ports: Object.fromEntries(Object.entries(input.invoicing).filter(([, fn]) => typeof fn === "function").map(([name, fn]) => [`@openlup/platform/invoicing:${name}`, fn])),
16
+ routes: Object.fromEntries(input.routes.map(route => [`@openlup/platform/invoicing:${route.id}`, route.handle])),
17
+ triggers: Object.fromEntries(input.schedules.map(schedule => [`@openlup/platform/invoicing:${schedule.id}`, schedule.run])),
18
+ };
19
+ }
@@ -0,0 +1,42 @@
1
+ import type { InvoiceWriteResult } from "./contracts/provider.js";
2
+ /** Durable correction identity, recovery and claim fencing from the donor worker.
3
+ * Hosts project immutable BEFORE facts and bind a conforming recovery provider.
4
+ * @consumer server/domains/accounting/accountingJobService.ts
5
+ */
6
+ export declare function runInvoiceCorrectionJob<Claim extends {
7
+ outboxId: string;
8
+ attemptCount: number;
9
+ providerInvoiceId: string;
10
+ correctionReason: string;
11
+ payload: Record<string, unknown>;
12
+ }, Snapshot, Result extends Pick<Extract<InvoiceWriteResult, {
13
+ kind: "issued";
14
+ }>, "providerDocumentRef" | "providerDocumentNumber">>(input: {
15
+ createEnabled: boolean;
16
+ providerEnabled: boolean;
17
+ claim(): Promise<readonly Claim[]>;
18
+ project(claim: Claim): Snapshot;
19
+ /** Resolve accepted/recovered receipts only; unknown is never an ACK or absence. */
20
+ create(snapshot: Snapshot, options: {
21
+ correctionReason: string;
22
+ correctedProviderInvoiceId: string;
23
+ correctionRef: string;
24
+ recoveryLookupRequired: boolean;
25
+ }): Promise<Result>;
26
+ finish(claim: Claim, result: Result): Promise<unknown>;
27
+ isClaimFenceError(error: unknown): boolean;
28
+ fail(claim: Claim, error: unknown, retrySeconds: number): Promise<unknown>;
29
+ }): Promise<{
30
+ ok: boolean;
31
+ checked: number;
32
+ updated: number;
33
+ skipped: boolean;
34
+ reason: string;
35
+ failures: number;
36
+ } | {
37
+ ok: boolean;
38
+ checked: number;
39
+ updated: number;
40
+ skipped: boolean;
41
+ failures: number;
42
+ }>;
@@ -0,0 +1,44 @@
1
+ /** Durable correction identity, recovery and claim fencing from the donor worker.
2
+ * Hosts project immutable BEFORE facts and bind a conforming recovery provider.
3
+ * @consumer server/domains/accounting/accountingJobService.ts
4
+ */
5
+ export async function runInvoiceCorrectionJob(input) {
6
+ if (!input.createEnabled)
7
+ return skipped("create_disabled");
8
+ if (!input.providerEnabled)
9
+ return skipped("provider_disabled");
10
+ const claims = await input.claim();
11
+ let updated = 0;
12
+ let failures = 0;
13
+ for (const claim of claims) {
14
+ try {
15
+ const correctionRef = `accounting-correction:${claim.outboxId}`;
16
+ if (claim.payload.providerCreateIdentity !== correctionRef) {
17
+ throw new Error("accounting_correction_recovery_identity_missing");
18
+ }
19
+ const snapshot = input.project(claim);
20
+ const result = await input.create(snapshot, {
21
+ correctionReason: claim.correctionReason,
22
+ correctedProviderInvoiceId: claim.providerInvoiceId,
23
+ correctionRef,
24
+ recoveryLookupRequired: claim.attemptCount > 1,
25
+ });
26
+ await input.finish(claim, result);
27
+ updated += 1;
28
+ }
29
+ catch (error) {
30
+ failures += 1;
31
+ if (input.isClaimFenceError(error))
32
+ continue;
33
+ try {
34
+ await input.fail(claim, error, Math.min(3600, 300 * Math.max(1, claim.attemptCount)));
35
+ }
36
+ catch (failureError) {
37
+ if (!input.isClaimFenceError(failureError))
38
+ throw failureError;
39
+ }
40
+ }
41
+ }
42
+ return { ok: failures === 0, checked: claims.length, updated, skipped: false, failures };
43
+ }
44
+ function skipped(reason) { return { ok: true, checked: 0, updated: 0, skipped: true, reason, failures: 0 }; }
@@ -0,0 +1,85 @@
1
+ import type { InvoiceUnsupported } from "./contracts/provider.js";
2
+ import type { InvoiceStorePort } from "./contracts/store.js";
3
+ import type { InvoiceDeliveryPort } from "./contracts/delivery.js";
4
+ type DeliveryOutcome = Omit<Extract<Awaited<ReturnType<InvoiceDeliveryPort["send"]>>, {
5
+ kind: "provider_accepted";
6
+ }>, "proof"> | Exclude<Awaited<ReturnType<InvoiceDeliveryPort["send"]>>, {
7
+ kind: "provider_accepted";
8
+ }>;
9
+ /** Donor sequencing over host claims, original keys, M04 transport and fenced
10
+ * finalizers. Native hosts MUST bind terminalFailure before leasing work.
11
+ * With CTL-M09-3's 191000 pin, failedBeforeSend settles failed_before_send with
12
+ * evidence { phase: 'before_send', retrySeconds }: whole seconds clamped
13
+ * to 60..86400, absent/invalid uses 900;
14
+ * terminalFailure uses { phase: 'before_send', retryable: false } for no reclaim.
15
+ * Native store mode refuses before claim unless its host projection is bound.
16
+ * @consumer Host delivery facade and explicit native M04 binding.
17
+ */
18
+ type DeliveryJobInput<Claim, Result extends DeliveryOutcome> = {
19
+ enabled: boolean;
20
+ providerEnabled: boolean;
21
+ deliveryEnabled: boolean;
22
+ claim(): Promise<readonly Claim[] | InvoiceUnsupported>;
23
+ attemptCount(claim: Claim): number;
24
+ validate(claim: Claim): void;
25
+ download(claim: Claim): Promise<{
26
+ content: Uint8Array;
27
+ contentType: string;
28
+ }>;
29
+ filename(claim: Claim): string;
30
+ /** A rejected promise proves failure BEFORE transport entry. Once entered,
31
+ * the M04 binding must return uncertain, including timeout/lost ACK. */
32
+ send(claim: Claim, document: {
33
+ filename: string;
34
+ content: Uint8Array;
35
+ contentType: string;
36
+ }): Promise<Result>;
37
+ failedBeforeSend(claim: Claim, error: unknown, retrySeconds: number): Promise<unknown>;
38
+ /** Must durably stop reclaim using the same fence. Native failed_before_send
39
+ * evidence { phase: 'before_send', retryable: false } sets available_at to infinity. */
40
+ terminalFailure(claim: Claim, result: Extract<Result, {
41
+ kind: "failed_before_send" | "unsupported";
42
+ }>): Promise<unknown>;
43
+ uncertain(claim: Claim, result: Extract<Result, {
44
+ kind: "uncertain";
45
+ }>): Promise<unknown>;
46
+ accepted(claim: Claim, result: Extract<Result, {
47
+ kind: "provider_accepted";
48
+ }>): Promise<unknown>;
49
+ finalizeUncertain(claim: Claim, receiptRef: string): Promise<unknown>;
50
+ };
51
+ type NativeClaim = Exclude<Awaited<ReturnType<InvoiceStorePort["claimDeliveries"]>>, InvoiceUnsupported>[number];
52
+ type NativeDeliveryJobInput = {
53
+ enabled: boolean;
54
+ providerEnabled: boolean;
55
+ deliveryEnabled: boolean;
56
+ store: Pick<InvoiceStorePort, "claimDeliveries" | "settleDelivery">;
57
+ claimOptions: Parameters<InvoiceStorePort["claimDeliveries"]>[0];
58
+ download(claim: NativeClaim): Promise<{
59
+ content: Uint8Array;
60
+ contentType: string;
61
+ }>;
62
+ filename(claim: NativeClaim): string;
63
+ send: InvoiceDeliveryPort["send"];
64
+ };
65
+ /** Host callback mode preserves donor sequencing. Native store mode supplies
66
+ * fenced terminal/backoff finalizers and the M04 envelope from claimDeliveries.
67
+ * @consumer Host delivery facade and explicit native M04 binding.
68
+ */
69
+ export declare function runInvoiceDeliveryJob(input: NativeDeliveryJobInput): ReturnType<typeof runDeliveryJob>;
70
+ export declare function runInvoiceDeliveryJob<Claim, Result extends DeliveryOutcome>(input: DeliveryJobInput<Claim, Result>): ReturnType<typeof runDeliveryJob>;
71
+ declare function runDeliveryJob<Claim, Result extends DeliveryOutcome>(input: DeliveryJobInput<Claim, Result>): Promise<{
72
+ ok: boolean;
73
+ checked: number;
74
+ updated: number;
75
+ skipped: boolean;
76
+ reason: string;
77
+ failures: number;
78
+ } | {
79
+ ok: boolean;
80
+ checked: number;
81
+ updated: number;
82
+ skipped: boolean;
83
+ failures: number;
84
+ }>;
85
+ export {};
@@ -0,0 +1,79 @@
1
+ import { assertInvoicePdf } from "./artifacts.js";
2
+ export function runInvoiceDeliveryJob(input) {
3
+ if (!("store" in input))
4
+ return runDeliveryJob(input);
5
+ const settle = async (claim, result, retrySeconds = 900) => {
6
+ const outcome = await input.store.settleDelivery({ deliveryRef: claim.delivery.deliveryRef, token: claim.token,
7
+ generation: claim.generation, providerDocumentRef: claim.providerDocumentRef, retrySeconds, result });
8
+ if (outcome.kind !== "recorded")
9
+ throw new Error("invoicing_delivery_fence");
10
+ };
11
+ return runDeliveryJob({
12
+ enabled: input.enabled, providerEnabled: input.providerEnabled, deliveryEnabled: input.deliveryEnabled,
13
+ claim: () => input.store.claimDeliveries(input.claimOptions), attemptCount: claim => claim.generation,
14
+ validate: () => { }, download: input.download, filename: input.filename,
15
+ send: (claim, document) => input.send({ ...claim.delivery, ...document }),
16
+ failedBeforeSend: (claim, _error, retrySeconds) => settle(claim, { kind: "failed_before_send", retryable: true, code: "retryable" }, retrySeconds),
17
+ terminalFailure: (claim, result) => settle(claim, result.kind === "failed_before_send" ? result
18
+ : { kind: "failed_before_send", retryable: false, code: "retryable" }),
19
+ uncertain: (claim, result) => settle(claim, result), accepted: (claim, result) => settle(claim, result),
20
+ finalizeUncertain: (claim, receiptRef) => settle(claim, { kind: "uncertain", code: "uncertain_delivery", receiptRef }),
21
+ });
22
+ }
23
+ async function runDeliveryJob(input) {
24
+ if (!input.enabled)
25
+ return skipped("provider_email_disabled");
26
+ if (!input.providerEnabled)
27
+ return skipped("provider_disabled");
28
+ if (!input.deliveryEnabled)
29
+ return skipped("email_delivery_provider_disabled");
30
+ // JS hosts missing a mandatory terminal policy must refuse before claiming.
31
+ if (typeof input.terminalFailure !== "function")
32
+ return skipped("host_binding_required");
33
+ const claims = await input.claim();
34
+ if (!Array.isArray(claims))
35
+ return skipped("host_binding_required");
36
+ let updated = 0;
37
+ let failures = 0;
38
+ for (const claim of claims) {
39
+ let result;
40
+ const retrySeconds = Math.min(3600, 300 * Math.max(1, input.attemptCount(claim)));
41
+ try {
42
+ input.validate(claim);
43
+ const pdf = await input.download(claim);
44
+ assertInvoicePdf(pdf);
45
+ result = await input.send(claim, { ...pdf, filename: input.filename(claim) });
46
+ }
47
+ catch (error) {
48
+ failures += 1;
49
+ await input.failedBeforeSend(claim, error, retrySeconds);
50
+ continue;
51
+ }
52
+ if (result.kind === "failed_before_send" || result.kind === "unsupported") {
53
+ failures += 1;
54
+ if (result.kind === "failed_before_send" && result.retryable) {
55
+ await input.failedBeforeSend(claim, result, retrySeconds);
56
+ }
57
+ else {
58
+ await input.terminalFailure(claim, result);
59
+ }
60
+ continue;
61
+ }
62
+ if (result.kind === "uncertain") {
63
+ failures += 1;
64
+ await input.uncertain(claim, result);
65
+ continue;
66
+ }
67
+ try {
68
+ await input.accepted(claim, result);
69
+ updated += 1;
70
+ }
71
+ catch {
72
+ failures += 1;
73
+ // Acceptance plus a lost local ACK cannot become a retryable send.
74
+ await input.finalizeUncertain(claim, result.receiptRef).catch(() => undefined);
75
+ }
76
+ }
77
+ return { ok: failures === 0, checked: claims.length, updated, skipped: false, failures };
78
+ }
79
+ function skipped(reason) { return { ok: true, checked: 0, updated: 0, skipped: true, reason, failures: 0 }; }