Skip to content

m12-pr11 TD-7: billing fail-closed in production#29

Open
BalaShankar9 wants to merge 1 commit into
m12-pr10-prompt-injection-defensefrom
m12-pr11-billing-fail-closed
Open

m12-pr11 TD-7: billing fail-closed in production#29
BalaShankar9 wants to merge 1 commit into
m12-pr10-prompt-injection-defensefrom
m12-pr11-billing-fail-closed

Conversation

@BalaShankar9

Copy link
Copy Markdown
Owner

TD-7 from the World Class Architecture Blueprint. Closes the silent fail-open in check_billing_limit's org-fetch path.

Problem

When Supabase (or whatever backs OrgService.get_user_orgs) hiccups, the existing handler swallowed the exception and treated the user as "no org" — which immediately short-circuited billing enforcement and let the request through unmetered. In production a backing-store blip silently disables every billing limit on the platform until the store recovers. Fail-open on a revenue-protecting check is unacceptable.

Fix

backend/app/api/deps.py:

  • New helper _billing_fail_closed_enabled():
    • Default: fail-closed when ENVIRONMENT=production, fail-open otherwise (preserves dev/test ergonomics — local runs without a seeded org keep working).
    • BILLING_FAIL_CLOSED env var (1/true/yes/on or 0/false/no/off) overrides in either direction so ops can flip without a redeploy and tests can pin the mode deterministically.
    • Read at call time so monkeypatch.setenv works in tests.
  • check_billing_limit now logs billing_org_fetch_failed (structured, includes user_id, feature, error head, and the fail_closed flag for forensics) and raises 503 when fail-closed is on. Existing 503 path on billing.check_limit() failure is preserved untouched.

Tests

backend/tests/unit/test_billing_fail_closed.py22 tests, 22 green:

  • _billing_fail_closed_enabled defaults (dev → False, production → True).
  • Env override forces fail-closed in dev / fail-open in production.
  • All truthy/falsy variants (1 / true / TRUE / Yes / on / 0 / false / no / off / "").
  • check_billing_limit:
    • Org-fetch failure fails open in dev (current behaviour, preserved).
    • Org-fetch failure raises 503 in production (the fix).
    • BILLING_FAIL_CLOSED=1 forces 503 even in dev.
    • BILLING_FAIL_CLOSED=0 forces fail-open even in production.
    • Genuine no-org user (solo mode) still skips enforcement.
    • Pre-existing 503 path on billing.check_limit() failure preserved.
    • Allowed → passes; Denied → 402.

Stack

... -> #27 (m12-pr09-feature-flag-audit) -> #28 (m12-pr10-prompt-injection) -> #THIS

Files: 3 changed, +224 / -2.

Blueprint TD-7 → SHIPPED.

Closes the silent fail-open in check_billing_limit's org-fetch path.

Problem
-------

When Supabase (or whatever backs OrgService.get_user_orgs) hiccups,
the existing handler swallowed the exception and treated the user
as "no org" — which immediately short-circuited billing
enforcement and let the request through unmetered. In production
that means a backing-store blip silently disables every billing
limit on the platform until the store recovers. Fail-open on a
revenue-protecting check is unacceptable.

Fix
---

backend/app/api/deps.py:
- New helper _billing_fail_closed_enabled():
  * Default: fail-closed when ENVIRONMENT=production, fail-open
    otherwise (preserves dev/test ergonomics — local runs without
    a seeded org keep working).
  * BILLING_FAIL_CLOSED env var (1/true/yes/on or 0/false/no/off)
    overrides in either direction so ops can flip without a
    redeploy and tests can pin the mode deterministically.
  * Read at call time so monkeypatch.setenv works in tests.
- check_billing_limit now logs billing_org_fetch_failed
  (structured, includes user_id, feature, error head, and the
  fail-closed flag for forensics) and raises 503 when fail-closed
  is on. Existing 503 path on billing.check_limit() failure is
  preserved untouched.

backend/tests/unit/test_billing_fail_closed.py: 22 tests covering
the helper (defaults, env overrides, truthy/falsy variants) and
check_billing_limit (fail-open in dev, fail-closed in production,
override flips both ways, no-org genuine path unchanged,
billing.check_limit 503 path preserved, allowed → pass, denied →
402).

Blueprint TD-7 -> SHIPPED.
Copilot AI review requested due to automatic review settings May 9, 2026 02:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens billing enforcement by preventing a fail-open path when the org lookup (Supabase-backed) errors, ensuring production requests don’t bypass billing limits during backing-store outages.

Changes:

  • Added _billing_fail_closed_enabled() to control fail-closed vs fail-open behavior based on ENVIRONMENT and BILLING_FAIL_CLOSED.
  • Updated check_billing_limit to log org-fetch failures and return 503 when fail-closed is enabled.
  • Added unit tests covering mode defaults, env overrides, and request-path behaviors for org-fetch/billing failures.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
docs/architecture/WORLD_CLASS_ARCHITECTURE_BLUEPRINT.md Marks TD-7 as shipped and documents the new billing fail-closed behavior.
backend/app/api/deps.py Implements fail-closed toggle + 503 behavior on org-fetch errors with structured logging.
backend/tests/unit/test_billing_fail_closed.py Adds unit tests validating toggle parsing and check_billing_limit behavior across scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/app/api/deps.py
Comment on lines +126 to +131
raw = os.getenv("BILLING_FAIL_CLOSED")
if raw is not None:
return raw.strip().lower() in {"1", "true", "yes", "on"}
return os.getenv("ENVIRONMENT", "development").strip().lower() == "production"


Comment thread backend/app/api/deps.py
Comment on lines +188 to +195
_billing_logger.error(
"billing_org_fetch_failed",
user_id=current_user.get("id"),
feature=feature,
error=str(exc)[:200],
fail_closed=_billing_fail_closed_enabled(),
)
if _billing_fail_closed_enabled():
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants