@voltro/plugin-billing 0.86.0 → 0.88.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 (44) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +428 -0
  3. package/THIRD-PARTY-NOTICES.md +1511 -276
  4. package/dist/catalog.js +1 -2
  5. package/dist/checkoutStore.js +164 -0
  6. package/dist/closeStore.js +124 -0
  7. package/dist/custodyInventory.js +154 -0
  8. package/dist/customerStore.js +258 -0
  9. package/dist/dunning.js +1 -2
  10. package/dist/economy.d.ts +30 -0
  11. package/dist/economy.js +265 -0
  12. package/dist/entitlement.js +1 -2
  13. package/dist/eventDelivery.js +71 -0
  14. package/dist/flushClaim.js +1 -2
  15. package/dist/index.d.ts +669 -65
  16. package/dist/index.js +230 -173
  17. package/dist/invoiceHistory.js +90 -0
  18. package/dist/invoiceHistoryJournal.js +475 -0
  19. package/dist/invoiceHistoryProgress.js +234 -0
  20. package/dist/invoiceHistoryWork.js +362 -0
  21. package/dist/invoiceSnapshot.js +65 -0
  22. package/dist/invoiceStore.js +132 -0
  23. package/dist/metering.js +1 -2
  24. package/dist/plans.js +1 -2
  25. package/dist/providerIdentity.js +23 -0
  26. package/dist/providers.js +271 -67
  27. package/dist/provision.js +1 -2
  28. package/dist/rpc.js +1 -2
  29. package/dist/service.js +892 -392
  30. package/dist/stores.js +125 -291
  31. package/dist/stripeClient.js +1 -2
  32. package/dist/stripeIdentity.js +55 -0
  33. package/dist/stripeInvoiceHistory.js +66 -0
  34. package/dist/stripeInvoiceSnapshot.js +85 -0
  35. package/dist/stripeProvider.js +305 -179
  36. package/dist/stripeRest.js +1 -2
  37. package/dist/stripeVerifiedEvent.js +28 -0
  38. package/dist/table.js +70 -48
  39. package/dist/types.d.ts +298 -27
  40. package/dist/types.js +1 -2
  41. package/dist/usageEvents.js +64 -0
  42. package/dist/web/index.js +1 -2
  43. package/dist/webhook.js +32 -40
  44. package/package.json +18 -8
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.