J
Skip to main content

Quota Gate — Manual Smoke Test

Run these checks against a staging tenant on the new tier model.

Pre-conditions

  • Tenant is on Solo plan with feature flag usage_quotas_v1 ON.
  • Current period is the calendar month.
  • Quota: jobs_created = 50, dvi_captures = 100, pdfs_rendered = 100, users_active = 3, bays_active = 2, storage_bytes = 5 GiB.

Jobs (50 → 51)

  1. POST 50 jobs in sequence → all 200.
  2. POST the 51st job → 403 with error.code = quota_exceeded, error.metric = jobs_created, error.used = 50, error.limit = 50.
  3. GET /api/billing/usage → metrics.jobs_created.used = 50.

DVI (100 → 101)

  1. POST 100 dvi result media upload-urls → all 200.
  2. POST the 101st → 403 QuotaExceeded, metric=dvi_captures.
  3. Counter in usage_counters table for tenant, period=YYYY-MM, metric=dvi_captures, count=100.

PDFs (100 → 101)

  1. GET 100 invoice PDFs → all 200.
  2. GET the 101st → 403 QuotaExceeded, metric=pdfs_rendered.

Users (3 → 4)

  1. With 3 active users, POST /api/invites → 403 QuotaExceeded, metric=users_active.

Bays (2 → 3)

  1. With 2 active bays, POST /api/jobs/workshop-bays → 403, metric=bays_active.

Plan switch (Solo → Pro)

  1. As Solo, fill jobs_created to 50/50.
  2. Switch plan to Pro (via the platform admin tool).
  3. Within 5s, GET /api/billing/usage → metrics.jobs_created.limit=1500.
  4. POST a job → 200.

Period rollover

  1. Fill jobs_created to 50/50.
  2. Override current_period() in the test to return "2026-07".
  3. POST a job → 200 (new month, fresh quota).
  4. usage_counters has a row for both 2026-06 (count=50) and 2026-07 (count=1).

Cache fail-open

  1. Disconnect Postgres from the API process (block port in iptables).
  2. POST a job → 200 with a tracing::warn in the logs ("quota_gate: no snapshot, passing through").
  3. Reconnect Postgres → next request rehydrates the cache.

Sign-off

  • All 8 sections pass.
  • Tracing logs show quota_gate: cache miss, passing through only during the fail-open test.
  • No 5xx responses in any of the 8 sections.

Rollout sequence (mirrors spec §10)

DayActionVerification
0feature_flag usage_quotas_v1 = off (default). New pricing.ts ships but /pricing still links the old page.Check /pricing page renders old tier card.
0–7Internal tenants only. Platform admin toggles flag ON per-tenant.Internal tenant dashboard shows UsageBanner at low usage.
7–21Pilot customers (GGDhaka, Hybrid Express, KL Auto Garage, Penang Moto Works). Flag ON per-tenant; 30-day "we won't enforce" grace period (operator can override any 403 manually).Pilot customers see banner; no real enforcement.
21/pricing flips to the new page. New signups land on the new tiers.Visit /pricing; verify 4-tier grid + ENTERPRISE_ONLY matrix.
60Grace ends. Quota gate enforces for all flag-enabled tenants.Run smoke-test section 1–5 against a flagged-on tenant; expect 403s.
24030-day countdown banner starts for legacy-plan tenants (showing "Your current plan ends in 30 days — pick a new tier").Visit /pricing as legacy tenant; banner visible.
270Legacy tenants migrated to new Starter tier at new price.All legacy tenants now have plan_slug = 'starter' in tenants / subscriptions.

Per-tenant override SQL

To enable the flag for a specific tenant (Day 0–7 and Day 7–21):

INSERT INTO tenant_features (tenant_id, feature_id, enabled)
SELECT '<tenant-uuid>', id, true
FROM features WHERE slug = 'usage_quotas_v1'
ON CONFLICT DO NOTHING;

To disable for a tenant (rollback):

UPDATE tenant_features SET enabled = false
WHERE tenant_id = '<tenant-uuid>'
AND feature_id = (SELECT id FROM features WHERE slug = 'usage_quotas_v1');

Global kill-switch

If a critical bug surfaces post-rollout, the platform team can disable globally:

UPDATE features SET enabled = false WHERE slug = 'usage_quotas_v1';

The change triggers feature_changed NOTIFY (via migration 016) and feature_cache reloads within seconds. The quota_gate middleware will then pass-through for all tenants.

Sign-off (rollout)

  • Day 0: smoke test passes; flag OFF globally.
  • Day 7: internal tenants have flag ON, all dashboards healthy.
  • Day 21: pilot customers on flag, /pricing flipped to new page.
  • Day 60: enforcement on, no 5xx spikes in observability.
  • Day 240: countdown banner live for legacy tenants.
  • Day 270: all legacy tenants on new Starter tier; legacy pro/enterprise plans deprecated in subscription_plans.