@voltro/plugin-billing 0.86.0 → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5321 -0
- package/README.md +428 -0
- package/THIRD-PARTY-NOTICES.md +860 -221
- package/dist/checkoutStore.js +165 -0
- package/dist/closeStore.js +125 -0
- package/dist/custodyInventory.js +155 -0
- package/dist/customerStore.js +259 -0
- package/dist/economy.d.ts +30 -0
- package/dist/economy.js +266 -0
- package/dist/eventDelivery.js +72 -0
- package/dist/index.d.ts +669 -65
- package/dist/index.js +229 -171
- package/dist/invoiceHistory.js +91 -0
- package/dist/invoiceHistoryJournal.js +476 -0
- package/dist/invoiceHistoryProgress.js +235 -0
- package/dist/invoiceHistoryWork.js +363 -0
- package/dist/invoiceSnapshot.js +66 -0
- package/dist/invoiceStore.js +133 -0
- package/dist/providerIdentity.js +24 -0
- package/dist/providers.js +270 -65
- package/dist/service.js +891 -390
- package/dist/stores.js +124 -289
- package/dist/stripeIdentity.js +56 -0
- package/dist/stripeInvoiceHistory.js +67 -0
- package/dist/stripeInvoiceSnapshot.js +86 -0
- package/dist/stripeProvider.js +304 -177
- package/dist/stripeVerifiedEvent.js +29 -0
- package/dist/table.js +69 -46
- package/dist/types.d.ts +298 -27
- package/dist/usageEvents.js +65 -0
- package/dist/webhook.js +31 -38
- package/package.json +17 -7
package/README.md
CHANGED
|
@@ -27,7 +27,435 @@ Checkout and portal URL routes recheck current authentication and their declared
|
|
|
27
27
|
manager guard after provider IO, before releasing the private URL. A refusal does
|
|
28
28
|
not undo or replay an already created external session.
|
|
29
29
|
|
|
30
|
+
Checkout creation reserves an active tenant operation in the Billing store,
|
|
31
|
+
retains its request/customer/deadline identity and reuses the provider key across
|
|
32
|
+
invocations. DataStore-backed applications retain that journal across restarts;
|
|
33
|
+
memory stores retain only process state. Providers return exact session receipts
|
|
34
|
+
and provider-clock lookup evidence. A missing mirror or host timestamp cannot
|
|
35
|
+
release an uncertain operation. Custom adapters and stores must adopt the
|
|
36
|
+
registered migration review; sales and provider setup are not activated by it.
|
|
37
|
+
|
|
38
|
+
Account closure first reserves an immutable account fence in `BillingStores.closures`,
|
|
39
|
+
blocking new checkout/change/resume/portal actions. After tenant-only final usage,
|
|
40
|
+
each subscription receives one retained provider-clock deadline and request identity.
|
|
41
|
+
Stripe retries reuse that deadline and SDK key, including after process replacement.
|
|
42
|
+
Native factories and `billingTables()` include the close journal; custom stores and
|
|
43
|
+
providers must adopt `BillingCloseStore`, `prepareSubscriptionClose` and the required
|
|
44
|
+
`closeOperation` for account-close cancellations. Keep these financial tombstones;
|
|
45
|
+
memory stores remain process-local. Pending/unknown results keep deletion pending.
|
|
46
|
+
In-flight provider actions, late usage and final invoice/payment settlement still
|
|
47
|
+
require lifecycle coordination; a canceled subscription is not a settlement receipt.
|
|
48
|
+
|
|
49
|
+
The account journal seals normal meter dispatch after the initial final flush.
|
|
50
|
+
Later billable usage is retained, blocks successful closure and needs financial
|
|
51
|
+
reconciliation. Global flushes skip sealed accounts; meter retries recheck the
|
|
52
|
+
seal. Custom close stores must implement monotonic `sealUsage`. In-flight writes,
|
|
53
|
+
provider aggregation receipts and late-usage invoice adjustments remain required.
|
|
54
|
+
|
|
55
|
+
Trusted server callers with a durable occurrence identity can use
|
|
56
|
+
`reportUsageEvent({ tenantId, entitlementKey, quantity, occurredAt, idempotencyKey })`.
|
|
57
|
+
The native usage store atomically commits the receipt and original-UTC-month counter;
|
|
58
|
+
matching replays do not count again, changed payloads are refused. Memory receipts
|
|
59
|
+
remain local. Custom UsageStore implementations need atomic `recordEvent`, and
|
|
60
|
+
custom schemas need `billingUsageEventsTable`. Native recording owns one root-store
|
|
61
|
+
transaction; do not nest application transactions or provider effects inside it.
|
|
62
|
+
Historical-month pending counters are retained for financial reconciliation rather
|
|
63
|
+
than flushed into the current month. Provider adjustments/settlement remain separate.
|
|
64
|
+
|
|
65
|
+
Checkout rows retain `slot: 'active'` while live, then use their immutable row id
|
|
66
|
+
for history. `(tenantId, slot)` is the non-null conflict/uniqueness key, independent
|
|
67
|
+
of nullable UNIQUE semantics. Custom stores preserve slot/state together; native
|
|
68
|
+
memory reads return validated copies. Existing custom/prototype receipts require
|
|
69
|
+
verified inventory/slot mapping before index changes; do not discard unknown rows.
|
|
70
|
+
|
|
71
|
+
Usage retention only expires unused counters (`used = 0` and
|
|
72
|
+
`reportedToProvider = 0`). Counters with consumption remain financial anchors,
|
|
73
|
+
including after provider dispatch; age does not establish financial settlement.
|
|
74
|
+
|
|
30
75
|
## License
|
|
31
76
|
|
|
32
77
|
Proprietary. © 2026 Voltro UG. Your use is governed by the
|
|
33
78
|
[Terms of Service](https://voltro.cloud/legal/terms). See the bundled `LICENSE`.
|
|
79
|
+
|
|
80
|
+
## Detailed invoice evidence
|
|
81
|
+
|
|
82
|
+
`BillingService.invoiceSnapshot(tenantId, providerInvoiceId)` performs a fresh,
|
|
83
|
+
customer-bound read through the optional `BillingProvider.fetchInvoiceSnapshot`
|
|
84
|
+
capability. Stripe reads every line page and rejects drafts, inconsistent headers,
|
|
85
|
+
duplicate cursors, malformed values or more than 10,000 lines. Exact decimal
|
|
86
|
+
quantities remain strings; unknown origins remain null. The service rechecks the
|
|
87
|
+
current customer link after the provider read. Adapters without the capability
|
|
88
|
+
fail explicitly. This server method still requires application authorization.
|
|
89
|
+
|
|
90
|
+
The result is an observation, not persisted payment or license authority. It
|
|
91
|
+
does not finalize/pay an invoice or change entitlements. Native ingestion and
|
|
92
|
+
subscription/price/payment qualification must precede paid-through decisions.
|
|
93
|
+
|
|
94
|
+
## Native invoice reconciliation
|
|
95
|
+
|
|
96
|
+
`BillingService.reconcileInvoice(tenantId, providerInvoiceId)` reads the native
|
|
97
|
+
revision, obtains a complete customer-bound provider snapshot, conditionally
|
|
98
|
+
persists it and reads it back. Invoice events use this path. Missing customer
|
|
99
|
+
links/capability or uncertain persistence fail explicitly for webhook retry.
|
|
100
|
+
A lost compare repeats BOTH reads, with bounded contention retries. Provider
|
|
101
|
+
calls stay outside database transactions. Applications still authorize callers.
|
|
102
|
+
|
|
103
|
+
`InvoiceStore` replaces event-payload upsert with get(binding),
|
|
104
|
+
complete(binding, expectedOrNull, snapshot) for exact InvoiceObservation records.
|
|
105
|
+
The unbounded native listByTenant method is removed. `_voltro_billing_invoice_observations` has immutable
|
|
106
|
+
ownership and a non-null unique provider/invoice digest. Retained invoice-event
|
|
107
|
+
history is never promoted. Apply declared schema, quiesce old writers and update
|
|
108
|
+
custom schema/store assemblies before cutover. The mock needs independent
|
|
109
|
+
setInvoiceSnapshot state; an event does not create provider evidence.
|
|
110
|
+
|
|
111
|
+
This persists observations, not payment receipts or license authority. Original
|
|
112
|
+
event callbacks remain historical notifications. Current subscription/price/
|
|
113
|
+
settlement qualification and native recovery still require separate validation.
|
|
114
|
+
|
|
115
|
+
## Invoice trigger ownership
|
|
116
|
+
|
|
117
|
+
`BillingProvider.normalizeEvent` returns `BillingProviderEvent`. Its invoice
|
|
118
|
+
variants carry exact providerCustomerId/providerInvoiceId, without tenantId.
|
|
119
|
+
Stripe ignores invoice and subscription metadata for this attribution. Missing
|
|
120
|
+
customer identity fails rather than acknowledging the event as unhandled.
|
|
121
|
+
|
|
122
|
+
`BillingService.applyEvent` resolves the unique native customer link, checks the
|
|
123
|
+
configured account/mode and retained customer ownership, reconciles complete invoice evidence,
|
|
124
|
+
and returns the tenant-resolved `BillingEvent` for onEvent. Missing, foreign or
|
|
125
|
+
ambiguous links fail; invoice triggers never create links. Custom providers and
|
|
126
|
+
service implementations must adopt both input and result contracts. Customer custody includes retained retired customers in the exact provider account/mode. Full invoice-history catch-up remains separate.
|
|
127
|
+
|
|
128
|
+
## Durable delivery and bounded catch-up
|
|
129
|
+
|
|
130
|
+
Live webhook and recovered events share one input-bound Framework receipt in
|
|
131
|
+
`_voltro_idempotency`, scoped by provider, plugin instance, observed account ID and mode. Complete after
|
|
132
|
+
reconciliation and callbacks; failed attempts release only their captured running
|
|
133
|
+
claim. Completed replay lasts 24 hours; retention can preserve rows longer. Native
|
|
134
|
+
Schema registration is required. Standalone webhook mounts keep process memory
|
|
135
|
+
unless `receipts: { scope, store }` supplies a shared IdempotencyStore.
|
|
136
|
+
|
|
137
|
+
Callbacks are at-least-once, including after expired claims, unknown commits and
|
|
138
|
+
receipt expiry. The optional delivery context provides provider/providerEventId/
|
|
139
|
+
receiptScope for business idempotency; it is absent on direct checkpoint/operator
|
|
140
|
+
corrections. Neither receipt ownership nor invoice observations establish payment
|
|
141
|
+
or license authority. A lease cannot stop an old executor's physical effects.
|
|
142
|
+
|
|
143
|
+
Provider listUndeliveredEvents now accepts cursor/limit and returns an explicit
|
|
144
|
+
page of exact normalized events plus nextCursor. Stripe reads at most 100 per page;
|
|
145
|
+
a pass visits at most 10 pages. Continuations, busy/failed events or failed reads
|
|
146
|
+
schedule a retry after 60 seconds. Fully processed exhausted history leaves no
|
|
147
|
+
recovery timer. Rejected events remain pending without preventing other event/page
|
|
148
|
+
work. Shutdown cancels pending retry and drains active recovery before returning.
|
|
149
|
+
Current customer discovery cannot replace another native binding or create a
|
|
150
|
+
missing link for a closed tenant. Historical custody is retained; migration, account cutover and recovery acceptance remain required.
|
|
151
|
+
|
|
152
|
+
The mount-local duplicate cache is isolated per mount by default. A successful
|
|
153
|
+
HTTP acknowledgement from older code is not an undelivered provider event. Review
|
|
154
|
+
that retained history separately; failed-delivery catch-up cannot recover invoices
|
|
155
|
+
that older code already acknowledged without recording complete evidence.
|
|
156
|
+
|
|
157
|
+
## Account and customer observations
|
|
158
|
+
|
|
159
|
+
BillingProvider.fetchAccountIdentity and fetchCustomerSnapshot are optional
|
|
160
|
+
read-only capabilities, implemented by Stripe and the mock. Custody consumers
|
|
161
|
+
must refuse unsupported providers rather than infer identity from a key prefix,
|
|
162
|
+
metadata, adapter name or an unqualified native row.
|
|
163
|
+
|
|
164
|
+
Stripe reads accounts.retrieve(null), balance.retrieve() and the account again.
|
|
165
|
+
It captures the returned account ID and the balance livemode flag. Customer reads
|
|
166
|
+
require an explicitly expected account/mode; mismatch fails before reading the
|
|
167
|
+
customer. The SDK response must name the exact customer, with matching livemode
|
|
168
|
+
for an undeleted Customer. Account/mode is checked again after the customer response.
|
|
169
|
+
Only an actual DeletedCustomer response records deleted:true; resource_missing,
|
|
170
|
+
authorization failures and network failures remain errors. These calls require
|
|
171
|
+
credentials permitting the corresponding SDK reads.
|
|
172
|
+
|
|
173
|
+
Snapshots copy primitive inputs before asynchronous calls and return independent
|
|
174
|
+
Date values. observedAt is local diagnostic time, not provider-clock evidence.
|
|
175
|
+
The SDK client must remain bound to one immutable credential/request context.
|
|
176
|
+
These are bounded multi-request observations, not an atomic provider transaction,
|
|
177
|
+
a settled payment or license authority; native customer custody is stored separately. General
|
|
178
|
+
sandbox namespace/cutover and real account/mode matrix acceptance remain required.
|
|
179
|
+
No existing customer row is automatically adopted or backfilled by these readers.
|
|
180
|
+
|
|
181
|
+
The mock owns a fresh nonsecret account ID per instance by default; accountId
|
|
182
|
+
provides an explicit stable fixture identity. Its mode is always test. Independent
|
|
183
|
+
setCustomerSnapshot state or ensureCustomer creates customer observations; invoice
|
|
184
|
+
or webhook payloads never manufacture them. Seed account/customer state explicitly
|
|
185
|
+
for recovery fixtures. Deleted customers are retained as observations; a later
|
|
186
|
+
ensureCustomer creates a different customer ID rather than reviving the old one.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
## Durable customer custody and account namespaces
|
|
190
|
+
|
|
191
|
+
Every tenant's financial namespace is pinned in `_voltro_billing_customer_accounts`
|
|
192
|
+
before first checkout, closure or usage mutation. It records provider, observed
|
|
193
|
+
account ID and mode. A different configured account/mode fails; no implicit
|
|
194
|
+
account switch, expiry or automatic adoption is supported.
|
|
195
|
+
|
|
196
|
+
`CustomerStore.bind` requires a positive, undeleted `fetchCustomerSnapshot`.
|
|
197
|
+
`_voltro_billing_customer_custody` uniquely binds each provider/account/mode/customer
|
|
198
|
+
to one tenant, and permits one current customer per tenant. Repeated binding of
|
|
199
|
+
that exact owner is idempotent. `retire` requires positive provider deletion and
|
|
200
|
+
keeps ownership permanently; later invoice observations use the retained owner,
|
|
201
|
+
never invoice metadata or a new current customer's identity. Discovery cannot
|
|
202
|
+
reactivate a retired customer. A new current link is refused while the tenant
|
|
203
|
+
still has live native subscriptions requiring reconciliation.
|
|
204
|
+
|
|
205
|
+
`fetchSubscriptionSnapshot` supplies direct customer-qualified observations,
|
|
206
|
+
including canceled subscriptions. Stripe and mock implement it; custom providers
|
|
207
|
+
must supply it for missing-list and retired-customer reconciliation. A null/404
|
|
208
|
+
or omission from a live list does not prove cancellation. Retired reconciliation
|
|
209
|
+
only mirrors positively canceled subscriptions of the retained customer; it
|
|
210
|
+
never restores plan grants, meter prices or the current customer slot. Provider
|
|
211
|
+
observations and native writes are not one transaction; concurrency, restart and
|
|
212
|
+
dialect acceptance remain required.
|
|
213
|
+
|
|
214
|
+
The current invoice table is `_voltro_billing_account_invoices`, whose identity
|
|
215
|
+
includes provider account and mode. Both earlier invoice tables and the earlier
|
|
216
|
+
customer table remain declared and intact. Apply `billingTables()` before the
|
|
217
|
+
updated code serves requests. Stop old writers before cutover. Existing financial
|
|
218
|
+
rows without a pinned namespace fail explicitly and require reviewed ownership
|
|
219
|
+
migration; this release supplies no automatic migration or namespace-switch tool.
|
|
220
|
+
Custom `CustomerStore` implementations must adopt `accountForTenant`,
|
|
221
|
+
`assertAccount`, `reserveAccount`, `bind`, `retire` and scoped lookup/list methods;
|
|
222
|
+
`upsert` and `removeByTenant` are removed.
|
|
223
|
+
|
|
224
|
+
Webhook signature verification also retrieves the exact event through the SDK,
|
|
225
|
+
checks account/mode and signed immutable event content, then uses an account-bound
|
|
226
|
+
version-2 delivery receipt. SDK credentials must permit event retrieval. Older
|
|
227
|
+
receipts are retained but never promoted across namespace versions. Callbacks
|
|
228
|
+
remain at-least-once. These records establish custody/observations, not settled
|
|
229
|
+
payment, financial completeness, sandbox namespace identity or license authority.
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## Retained-custody inventory
|
|
233
|
+
|
|
234
|
+
`inspectBillingCustody(rootStore, { tenantId, pageSize?, maxRowsPerTable? })` is a
|
|
235
|
+
trusted server/operator read utility, not an RPC or an adoption API. Authorize the
|
|
236
|
+
operator and tenant first; invoke outside an existing transaction. Mandatory
|
|
237
|
+
integrity reads cover the explicit tenant's original/current billing tables,
|
|
238
|
+
including deletion/expiry states. Two complete bounded keyset sweeps run in one
|
|
239
|
+
native transaction; changed content, query failures, duplicate IDs, foreign tenant
|
|
240
|
+
rows and unsupported evidence fail rather than produce partial review output.
|
|
241
|
+
|
|
242
|
+
The review-only result contains content/table/row SHA-256 digests, row IDs and
|
|
243
|
+
existing provider identifiers, without raw financial payloads. Date values become
|
|
244
|
+
ISO strings; unsafe numeric/non-JSON values fail rather than lose identity. Default
|
|
245
|
+
page size is 200 (1–500), default per-table bound 5,000 (1–50,000); encoded limits
|
|
246
|
+
are 4 MiB per row and 64 MiB per sweep. The digest excludes diagnostic observedAt.
|
|
247
|
+
|
|
248
|
+
Equal read sweeps do not prove a writer barrier, schema readiness, complete
|
|
249
|
+
restoration, provider state, original account ownership or settled financial
|
|
250
|
+
history. Global receipts, provider history and application Cloud records remain
|
|
251
|
+
separate inputs. No account/custody write occurs. The explicit original-evidence,
|
|
252
|
+
approval/apply, adoption and account/environment cutover workflow is still required.
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
## Account-bound invoice history pages
|
|
256
|
+
|
|
257
|
+
`BillingProvider.listInvoiceHistory` is an optional read capability implemented by
|
|
258
|
+
Stripe and mock. Inputs are `{ account, providerCustomerId, cursor?, limit? }`.
|
|
259
|
+
Limit defaults to100 and must be an integer1–100. Each cursor retains account ID,
|
|
260
|
+
mode, exact customer and afterInvoiceId. Wrong scope or malformed/repeated page
|
|
261
|
+
identities fail; there is no fallback to the bounded listInvoices preview.
|
|
262
|
+
|
|
263
|
+
Stripe reads account/mode before and after the page, verifies an incoming cursor's
|
|
264
|
+
invoice belongs to that customer, and uses official SDK invoice pagination with
|
|
265
|
+
no delivery-success or invoice-status filter. Paid, open, void, uncollectible and
|
|
266
|
+
draft references are included. The page exposes invoices plus explicit nextCursor,
|
|
267
|
+
account/customer and local observedAt. IDs, status, customer/mode, created dates,
|
|
268
|
+
minor-unit amounts, currency and nullable HTTPS provider links are validated.
|
|
269
|
+
Future unknown statuses fail explicitly. Omitted hosted/PDF links become null;
|
|
270
|
+
missing identity/status never becomes an invented ID or unknown status.
|
|
271
|
+
|
|
272
|
+
Server code can call `BillingService.invoiceHistory(tenantId, customerId, options)`
|
|
273
|
+
for a positively owned current or retained customer. The service checks account
|
|
274
|
+
and immutable native custody around the provider read. Authorize the caller first;
|
|
275
|
+
no new public RPC is installed. Custom service implementations must add this
|
|
276
|
+
method or fail explicitly. `reconcileInvoice` now publicly accepts an optional
|
|
277
|
+
third exact customer ID for retained-owner observations; never ignore that identity
|
|
278
|
+
in custom implementations.
|
|
279
|
+
|
|
280
|
+
`listInvoices` and service.invoices remain recent previews (20 by default for
|
|
281
|
+
built-in providers), not complete history or lifetime totals. Stripe and mock use
|
|
282
|
+
the same validated page reader for those previews. The mock requires independent
|
|
283
|
+
`setInvoiceHistory` state with original createdAt; setInvoiceSnapshot and webhook
|
|
284
|
+
payloads do not create history or customer ownership. Seed all three independently
|
|
285
|
+
when a fixture claims each plane.
|
|
286
|
+
|
|
287
|
+
A page is a reference list, not complete invoice/line evidence or settled payment.
|
|
288
|
+
A null nextCursor exhausts this traversal; pages are not an atomic provider snapshot
|
|
289
|
+
or proof that future/late invoices cannot appear. Persist walk progress and every
|
|
290
|
+
failed/pending reference before advancing, revisit drafts and new head entries,
|
|
291
|
+
and reconcile finalized invoices with complete snapshots. That durable backfill,
|
|
292
|
+
previous-successful-ACK recovery, full financial qualification and native/provider/
|
|
293
|
+
restart/replica/browser/start acceptance remain required and are not completed by
|
|
294
|
+
this read-only capability.
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
### Durable invoice-history page admission
|
|
298
|
+
|
|
299
|
+
`dataStoreInvoiceHistoryJournal(rootStore)` provides `begin(owner)`, `read(owner)`,
|
|
300
|
+
`commitPage(expectedWalk, page)`, `restart(expectedWalk)` and
|
|
301
|
+
`references(owner, { afterReferenceId?, limit? })`. The owner must already be an
|
|
302
|
+
actual current or retained `CustomerRecord` in the same native namespace.
|
|
303
|
+
No row is adopted from an event, metadata, old acknowledgement or current SDK
|
|
304
|
+
credentials. Obtain account/customer-confirmed pages through `invoiceHistory`
|
|
305
|
+
outside the native transaction; journal admission does not contact providers.
|
|
306
|
+
|
|
307
|
+
A page and every invoice reference commit in one root-store transaction before
|
|
308
|
+
cursor advancement. Native uniqueness, root locking, expected revision and
|
|
309
|
+
readback protect the page boundary; a stale checkpoint returns null without
|
|
310
|
+
writes. Repeated cursors or cross-page invoice IDs in one round refuse the
|
|
311
|
+
whole page. Restart retains every prior page/reference and monotonically
|
|
312
|
+
orders new pages, including new-head scans. Discovery keeps drafts and all
|
|
313
|
+
supported invoice statuses without creating invoice observations, payments,
|
|
314
|
+
licenses or settlement claims.
|
|
315
|
+
|
|
316
|
+
`billingTables()` includes `billingInvoiceHistoryWalksTable`,
|
|
317
|
+
`billingInvoiceHistoryPagesTable` and `billingInvoiceHistoryReferencesTable`.
|
|
318
|
+
Custom schemas must include them too. The retained-custody roster includes all
|
|
319
|
+
17 tables. Missing roots are not recreated over orphan receipts. Bounds and
|
|
320
|
+
native aggregate counts reject missing pages/references; returned references
|
|
321
|
+
are checked against their complete page and digest. These integrity reads
|
|
322
|
+
inspect retained native indexes and counts; their cost grows with history and
|
|
323
|
+
still needs real-dialect performance/recovery validation.
|
|
324
|
+
|
|
325
|
+
`exhausted` only records the end of provider enumeration. A reference page's
|
|
326
|
+
watermark and null next cursor describe its captured native read, not a global
|
|
327
|
+
or financial completion receipt. Automatic scan scheduling and due-queue enumeration are still to be
|
|
328
|
+
connected. Per-reference processing and durable results are described below. No existing `BillingService` method is promoted to automatic
|
|
329
|
+
backfill by this primitive.
|
|
330
|
+
|
|
331
|
+
After a stale `commitPage` result, discard the offered page and reread both the
|
|
332
|
+
native checkpoint and the provider page for that exact cursor. Never relabel a
|
|
333
|
+
captured page with a newer checkpoint or a restarted head.
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
### Process a retained invoice reference
|
|
337
|
+
|
|
338
|
+
`BillingService.processInvoiceHistoryReference(tenantId, customerId, referenceId)`
|
|
339
|
+
verifies actual retained customer custody and the exact journal reference,
|
|
340
|
+
then reads/reconciles the complete invoice through the existing provider path.
|
|
341
|
+
The caller still authorizes access. This is a server method, not a new RPC or
|
|
342
|
+
HTTP route. The reconciliation phase has a five-minute Effect timeout; timeout
|
|
343
|
+
or interruption does not prove a dispatched SDK/database action stopped.
|
|
344
|
+
|
|
345
|
+
`BillingStores` now requires `historyJournal` and `historyWork`. Use the same
|
|
346
|
+
root as `customers`, `invoices` and Framework idempotency. `memoryStores()`
|
|
347
|
+
uses these native adapters together on one actual InMemoryDataStore, including
|
|
348
|
+
standard Framework receipt metadata. Custom schema assemblies must include
|
|
349
|
+
`billingInvoiceHistoryWorkTable` and the standard `_voltro_idempotency` table;
|
|
350
|
+
`billingTables()` includes the work table and retained-custody inventory now
|
|
351
|
+
contains 17 tenant tables. Do not compose unrelated Memory ports for this flow.
|
|
352
|
+
|
|
353
|
+
Work has durable queued/working/retry/deferred/observed state, logical attempt
|
|
354
|
+
and execution counters, next-attempt time and an actual native observation
|
|
355
|
+
ID/version. Framework claims and their standard heartbeat own the running
|
|
356
|
+
execution. Completing that exact claim and the business acknowledgement share
|
|
357
|
+
one native transaction; a reclaimed executor cannot publish a result. A
|
|
358
|
+
completed receipt without its matching native work never manufactures an
|
|
359
|
+
observation: a later attempt must reread the provider. Work results survive
|
|
360
|
+
receipt expiry. Fresh reads/reconciliation may run more than once.
|
|
361
|
+
|
|
362
|
+
Open invoices remain deferred; drafts and unconfirmed/error/timeout reads
|
|
363
|
+
remain retryable after one minute. An observed work result is not payment,
|
|
364
|
+
settlement, original-price or historical-license coverage. Reusing an observed
|
|
365
|
+
result checks its actual native invoice ID/version and status; missing or
|
|
366
|
+
rewound evidence queues a fresh attempt. Automatic discovery/due scheduling,
|
|
367
|
+
startup/replica orchestration and full financial qualification are not part of it.
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
### Bounded invoice work selection
|
|
371
|
+
|
|
372
|
+
`historyWork.pending(owner, lane, now, { limit, cursor })` reads at most 100
|
|
373
|
+
entries from an actual owned walk (25 by default). Use the independent
|
|
374
|
+
`unprepared`, `ready` and `working` lanes: new references, due queued/retry/deferred
|
|
375
|
+
work, and claimed executions whose stored retry time has arrived. Selection
|
|
376
|
+
never acquires a claim. Dispatch still calls `processInvoiceHistoryReference`,
|
|
377
|
+
which independently checks the current Framework receipt and native custody.
|
|
378
|
+
An entry in `working` can still have a renewed live claim and return busy.
|
|
379
|
+
|
|
380
|
+
Cursors belong to their retained walk and lane. Continue each lane separately;
|
|
381
|
+
revisit its head in later passes so newly inserted work is included. A null
|
|
382
|
+
cursor ends this bounded read, not invoice enumeration, pending work, payment
|
|
383
|
+
or historical financial coverage. Unprepared selection uses a native
|
|
384
|
+
anti-subquery instead of loading every old reference. Selected references are
|
|
385
|
+
validated in one batch against their complete retained pages, sharing the
|
|
386
|
+
journal's actual native integrity checks. Total retained-history/index cost
|
|
387
|
+
still needs native performance measurement.
|
|
388
|
+
|
|
389
|
+
This server store primitive does not start a worker. Automatic retained-customer
|
|
390
|
+
inventory, due-queue dispatch, durable orchestration cursors, new-head rounds
|
|
391
|
+
and startup/replica/shutdown integration remain required. Custom
|
|
392
|
+
`InvoiceHistoryWorkStore` implementations must provide `pending`; a fabricated
|
|
393
|
+
empty page is not a recovery implementation.
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
### Current and retained customer inventory
|
|
397
|
+
|
|
398
|
+
`customers.inventory(scope, { afterCustodyId, limit })` reads actual current
|
|
399
|
+
and retired custody records in one exact provider account and mode. It does
|
|
400
|
+
not change `customers.list(scope)`, which continues to list current customers.
|
|
401
|
+
Pages default to 25 records and allow at most 100, with stable custody-ID
|
|
402
|
+
continuation. A retained cursor stays usable when its customer is retired.
|
|
403
|
+
Unknown or differently scoped cursors refuse; every returned record is checked
|
|
404
|
+
against its independent native tenant/account intent and custody identity.
|
|
405
|
+
Unqualified legacy customer rows refuse inventory rather than disappear behind
|
|
406
|
+
an apparently empty current-account result.
|
|
407
|
+
|
|
408
|
+
A null continuation only ends this account-bound read. Later passes must revisit
|
|
409
|
+
the head for newly inserted IDs. It neither adopts an original account namespace
|
|
410
|
+
nor establishes complete invoice/payment/license coverage. Inventory requires
|
|
411
|
+
root server visibility, not a tenant-filtered request store. The standard custody
|
|
412
|
+
table includes `byBillingCustomerCustodyInventory` for provider/account/mode/ID
|
|
413
|
+
ordering; use normal reviewed schema migration. Existing rows need no rewritten
|
|
414
|
+
ownership, and this index does not migrate unqualified historical data.
|
|
415
|
+
|
|
416
|
+
Custom `CustomerStore` ports must provide `inventory` including retained retired
|
|
417
|
+
ownership. `memoryCustomerStore()`, `memoryStores()` and `dataStoreStores(root)`
|
|
418
|
+
share the native implementation. Automatic durable traversal, invoice dispatch
|
|
419
|
+
and startup/replica/shutdown orchestration are still required; native dialect,
|
|
420
|
+
concurrency/restore and growing-history performance remain unproven.
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
### Durable bounded history passes
|
|
424
|
+
|
|
425
|
+
`BillingService.reconcileInvoiceHistory()` connects native account/customer
|
|
426
|
+
inventory, page admission and reference work in one bounded server pass: one
|
|
427
|
+
current or retained customer, at most one provider page of 100 entries, and one
|
|
428
|
+
entry from each independent unprepared/ready/working lane. Account and lane
|
|
429
|
+
continuations live in `historyProgress` over the same native root. A fresh
|
|
430
|
+
service and fresh adapters resume those positions. Positions use native
|
|
431
|
+
revision CAS and validate retained customer/walk/cursor identity; they neither
|
|
432
|
+
own work execution nor qualify financial completion. Actual work still uses
|
|
433
|
+
Framework claims and fresh owned reconciliation. Unchanged traversal positions
|
|
434
|
+
retain their native revision and update time; an idle pass is not a heartbeat
|
|
435
|
+
write.
|
|
436
|
+
|
|
437
|
+
A customer-specific failure remains unconfirmed and does not starve the other
|
|
438
|
+
customers. Each lane revisits its head after reaching its current end. The
|
|
439
|
+
pass has a two-minute Effect timeout; interruption stops the reference
|
|
440
|
+
heartbeat, but does not prove an already dispatched SDK/database operation
|
|
441
|
+
stopped. Invoice reads/writes remain bound to their captured store; a rebind
|
|
442
|
+
detected during provider observation refuses acknowledgement before writing
|
|
443
|
+
the replacement root.
|
|
444
|
+
|
|
445
|
+
`BillingStores` requires `historyProgress`. Standard `billingTables()` adds
|
|
446
|
+
`billingInvoiceHistoryScanAccountsTable` and `billingInvoiceHistoryDispatchTable`.
|
|
447
|
+
The dispatch table joins the 17 tenant-addressable custody tables; the scan
|
|
448
|
+
account table is global account-scoped traversal metadata and needs separate
|
|
449
|
+
inventory alongside other global receipts. Both progress tables are
|
|
450
|
+
destination-local during logical transfer. Default shared Memory and SQL stores
|
|
451
|
+
use the same native adapters. Custom ports must persist actual progress and
|
|
452
|
+
validate its pointers, not return fabricated completed/empty positions.
|
|
453
|
+
|
|
454
|
+
This is a server service method, with no new HTTP/RPC/inspect entry point. It
|
|
455
|
+
does not start an automatic lifecycle worker or restart exhausted enumeration
|
|
456
|
+
at new heads. Startup/replica/shutdown scheduling, fresh new-head admission
|
|
457
|
+
with bounded retained-receipt growth, original namespace adoption and full
|
|
458
|
+
financial qualification remain required. Idle/advanced/contended results and
|
|
459
|
+
counts describe a bounded pass only. Native rollback/isolation/restore,
|
|
460
|
+
performance, provider/browser and normal development/compiled-start acceptance
|
|
461
|
+
remain unproven.
|