Skip to content

feat(billing): consume Stripe reconciliation triggers with leases - #574

Draft
seonghobae wants to merge 18 commits into
feat/stripe-checkout-identity-bootstrap-488from
feat/stripe-reconciliation-worker-488
Draft

feat(billing): consume Stripe reconciliation triggers with leases#574
seonghobae wants to merge 18 commits into
feat/stripe-checkout-identity-bootstrap-488from
feat/stripe-reconciliation-worker-488

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Buyer/reliability objective

Refs #488. This stacked Draft consumes the durable verified-event reconciliation triggers produced by #570/#573 without treating webhook arrival order as lifecycle or entitlement authority. It adds finite worker leases, bounded retry/backoff, durable attempt evidence, explicit dead-letter state, and a production bootstrap that invokes the existing authoritative Subscription/Invoice reconciliation service only after resolving tenant authority from server-owned normalized Stripe identity.

Exact current stack

  • protected develop: 2c328875e00e86537df3e965170be80532571cad;
  • exact parent/base feat(billing): bootstrap Stripe Checkout tenant identity #573: feat/stripe-checkout-identity-bootstrap-488@7e480f0c10ac5598e641fff03d6f7d7a8afca8f2;
  • exact contributor head at opening: 3e6b0eb17a063bdd2f21f856f362c5ac63a9b24f;
  • fresh parent→child comparison immediately before opening: ahead 9, behind 0, exact parent as merge base, seven changed paths;
  • state: Draft.

Any parent, contributor-head, protected-base, review, or check movement invalidates ancestry- and head-sensitive evidence until freshly revalidated.

Worker and data-integrity contract

server/stripe_reconciliation_worker.mjs adds two normalized relations while preserving the immutable trigger table:

  • billing_stripe_reconciliation_jobs keeps one current scheduling head per verified Event trigger; and
  • billing_stripe_reconciliation_attempts keeps append-only attempt/lease outcome evidence.

The worker lazily seeds jobs from already-durable triggers, claims one ready trigger under an opaque finite lease, stores only the SHA-256 lease-token digest, prevents concurrent double processing, reclaims expired leases, rejects stale completion, retries with capped exponential backoff, and moves permanently failing work to explicit dead_letter after a finite attempt budget. Arbitrary provider exception text is never persisted; only bounded machine-readable stripe_* codes are retained, otherwise failure collapses to stripe_reconciliation_failed.

Tenant authority is not caller-selectable. The repository resolves organization identity through the existing normalized billing_stripe_subscriptionsbilling_stripe_customers.organization_id chain. Missing identity remains explicit retryable work and does not call the provider reconciliation port.

runNextStripeReconciliationJob then invokes the existing authoritative reconciliation service with exact organization, Subscription, and verified Event provenance. Its receipt must match that tenant and Subscription before the lease can complete. This slice never writes orgs.plan, membership/RBAC, browser sessions, or capabilities.

Bootstrap and transaction boundary

server/db.mjs installs worker state after the existing trigger/evidence/claim prerequisites and exports reconcileNextStripeBillingTrigger(), configured with the production Subscription observation, Invoice observation, and entitlement-claim repositories.

Worker state changes use named SQLite savepoints. Success releases only after the complete mutation; failure uses ROLLBACK TO SAVEPOINT and releases only after rollback is confirmed, preserving the causal error. Final-attempt lease expiry is terminal dead-letter evidence rather than an unclaimable pending row.

TDD and causal repair chronology

  • test-only commit 5e8e673c5f103868e9edfaacc0853a802562bb71 introduced a realistic worker lifecycle contract while the production module was absent, establishing a deterministic module-resolution RED;
  • production implementation then added leased claiming/completion/retry behavior;
  • fresh source/schema verification found the first implementation had copied a test-only org_id alias instead of the real billing_stripe_customers.organization_id production schema;
  • test commit b6fa011ae3c0575056c471cd07e16d8928af52c5 replaced the custom identity tables with the actual production Subscription observation schema, making schema drift executable evidence;
  • source repair c022d632949d14a1664e00780bff7792ec4d1962 binds tenant lookup to organization_id and also prevents final-attempt lease expiry from leaving permanently pending but unclaimable work.

The focused behavior covers successful worker completion, lease exclusion/reclaim and stale-worker rejection, bounded retry/dead-letter, secret-text non-persistence, and missing-identity retry without provider invocation. package.json and tests/unit/coverage-script-contract.test.mjs lock the worker into normal unit CI and canonical c8 instrumentation/cases.

Standards and traceability

docs/doctoring/stripe-reconciliation-worker.md records active-PR versus protected-shipped truth, normalized state ownership, lease secrecy, retry/dead-letter policy, savepoint semantics, rollback, privacy boundaries, and APA 7 references to current Stripe webhook delivery guidance plus SQLite transaction/savepoint documentation.

Stripe documents automatic retries and explicitly does not guarantee event delivery ordering. This implementation therefore treats events only as durable reconciliation triggers and re-fetches current provider state through the pre-existing #569 service before claim evaluation.

Current evidence boundary

Opening this PR creates fresh repository/organization checks for this contributor head. Until every applicable exact-head deterministic/browser/statement/branch/function/line coverage/docstring/CodeQL/SAST/security/dependency/supply-chain/package/provenance gate is terminal and substantive, valid findings are zero, and qualifying independent current-head approval exists under live governance, pending/queued/absent/stale/predecessor/status-only/model-only evidence is non-passing.

Remain Draft and do not integrate independently of #573/#570/#569/#568 and the preceding #488 billing stack. This slice does not add a perpetual scheduler or operator dead-letter recovery API/UI; those and final out-of-order end-to-end convergence/release acceptance remain subsequent bounded work.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2707147c-2af9-452f-857a-1dae1245c3cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@opencode-agent opencode-agent Bot added priority: medium Normal-priority or P2 work status: draft Draft pull request type: feature New or expanded product capability labels Aug 22, 2026
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: medium Normal-priority or P2 work status: draft Draft pull request type: feature New or expanded product capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant