User paid. Stripe looked fine. The app still gated them.
They had an active subscription. The checkout page worked. Tests passed. But has_paid_access in Postgres stayed false, so the user hit the paywall anyway.
This is not a rare edge case. It is the default failure mode when you ship billing logic with AI tools and mock Stripe in tests.
How the bug actually happens
The sequence is usually boring:
- Agent writes checkout and a webhook handler for
customer.subscription.created. - Tests mock Stripe. CI goes green.
-
invoice.payment_failedorcustomer.subscription.deletednever updates the DB flag. - Stripe says active. Your app says free.
Nothing in a unit test suite checks both systems at once. You are testing each side in isolation. The bug lives in the gap between them.
Stack Overflow's 2025 survey put AI coding tool adoption around 84%. Veracode found roughly 45% of AI-generated code samples contained OWASP Top 10 issues. Only about 33% of developers say they trust AI accuracy. Speed went up. The intersection of billing and database state did not get safer.
What v0.10.0 changed
Before v0.10, showing someone this bug took four steps: clone the SDK, cd into it, pass config flags, point at a fixtures directory. Fine for docs. Too much friction for a Reddit comment or a tweet.
v0.10.0 bundles the revenue-leak fixture inside the CLI:
npx prodverdict@0.10.0 demo
One command. No git clone. No Stripe keys. You get a FAIL on purpose: active subscription, has_paid_access false.
Two other commands round out discovery:
npx prodverdict scan # static repo analysis, no credentials
npx prodverdict init --mcp --cursor-rule # auto-detects stack from package.json
Architecturally, v0.10 adds a discovery layer on top of the existing engine. runContracts() still powers everything. The new commands just remove the setup tax before someone sees value.
The fix: check the intersection in CI
ProdVerdict compares billing state (Stripe or Paddle) against who your database thinks has paid access. Rules only. No LLM in the evaluation path. Missing credentials = fail, not a silent pass.
After the demo, wire it into your repo:
npx prodverdict init --mcp --cursor-rule
export STRIPE_SECRET_KEY=rk_test_...
export DATABASE_URL=postgresql://readonly:...@host/db
npx prodverdict check access
Block merges in GitHub Actions:
- uses: prodv-dev/prodverdict-action@v0.10.0
with:
config: ./prodverdict.yml
contract: access
env:
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Secrets stay on your runner. ProdVerdict cloud never sees subscription rows.
Other contracts (after access is wired)
Once the billing check is trusted, add more: config (env drift), migration (unsafe Postgres DDL), boundary (mass-assignment), webhook (signature + idempotency lint), restore (backup smoke tests). Run all six with npx prodverdict check all.
Start with access. It ties directly to revenue. The rest is expansion.
Try it
npx prodverdict@0.10.0 demo
- Docs: https://prodverdict.com/docs/quickstart
- Agents + MCP: https://prodverdict.com/agents
- SDK: https://github.com/prodv-dev/prodverdict-sdk
- GitHub Action: https://github.com/marketplace/actions/prodverdict
Free CLI, Action, and local MCP on private repos. I built this after catching the same bug one too many times. Curious if you have shipped this class of mismatch before.

Top comments (0)