Files
Momento/_bmad-output/implementation-artifacts/spec-subscription-billing-admin-management.md
Antigravity ee70e74bf5
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m39s
CI / Deploy production (on server) (push) Successful in 22s
fix: 5 bugs critiques de l'éditeur (Phase 1 audit)
1. replaceAll (Find & Replace) — une seule transaction ProseMirror
   au lieu d'un forEach cassé. Tous les matchs sont maintenant remplacés.

2. Link Preview unwrap — deleteNode() au lieu de clearer les attrs
   qui laissaient un nœud fantôme invisible dans le document.

3. Conversion Markdown → richtext — breaks: true dans marked.parse()
   Les simple newlines sont maintenant convertis en <br>.
   + préserve les blocs custom (toggle, callout, math, columns,
   outline, link-preview) en commentaires HTML lors de l'export MD.

4. emitNoteChange exercices — shape corrigée (type:'created' attend
   un objet Note, pas noteId/notebookId séparés).

5. Raccourcis clavier sans conflit :
   Cmd+Shift+C → Cmd+Alt+C (callout, avant: copier)
   Cmd+Shift+O → Cmd+Alt+O (outline, avant: historique/signets)
   Cmd+Shift+L → Cmd+Alt+L (colonnes, avant: lock screen macOS)
2026-06-20 15:48:18 +00:00

11 KiB
Raw Permalink Blame History

title, type, created, status, baseline_commit, context
title type created status baseline_commit context
Subscription & Quota Admin Management feature 2026-06-20 done 5b13a88b72
memento-note/lib/entitlements.ts
memento-note/lib/config.ts
memento-note/lib/billing/stripe-prices.ts

Intent

Problem: Subscription tiers, AI quotas, and Stripe business config are split between hardcoded code (TIER_LIMITS), server .env (price IDs, billing flag), and a minimal admin override (tier dropdown only). Operators cannot adjust limits or billing settings without redeploy; quota enforcement has a TOCTOU race (check then increment); token analytics are partially broken.

Approach: Add a secure admin Billing & Quotas console backed by DB (PlanEntitlement + SystemConfig), keep Stripe secrets in infra env only, unify quota enforcement on atomic reserveUsageOrThrow, and expose usage dashboards with audit logging for manual tier overrides.

Boundaries & Constraints

Always:

  • STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET stay in server env / secrets vault — never in SystemConfig, never editable from browser.
  • Stripe remains authoritative for paid subscriptions; webhooks drive state; manual admin tier changes are support overrides only.
  • Quota gating unit stays requests per feature per calendar month (not token-based blocking).
  • Redis quota checks fail-open with structured error logging (industry standard for AI rate limits).
  • All admin mutations require role === ADMIN; i18n for new UI strings in locales/en.json and locales/fr.json minimum.
  • Non-destructive DB migration only; seed PlanEntitlement from current TIER_LIMITS defaults.

Ask First:

  • Adding new Prisma models beyond PlanEntitlement (e.g. full FeatureFlag wiring).
  • Changing Stripe products/prices in live Stripe Dashboard (ops action, not code).
  • Switching to usage-based Stripe Meter / Metronome billing.

Never:

  • Store sk_* or whsec_* in plaintext DB or admin forms.
  • Run prisma migrate reset, db push --accept-data-loss, or any destructive DB command.
  • Replace Redis real-time counters with synchronous PostgreSQL writes on every AI request.
  • Add automated tests unless explicitly requested later.

I/O & Edge-Case Matrix

Scenario Input / State Expected Output / Behavior Error Handling
Admin saves tier limit ADMIN edits PRO chat limit 50→75 PlanEntitlement upserted; next getLimit() returns 75; existing Redis counters unchanged until period rollover 403 if non-admin; 400 if invalid feature or negative limit
Admin saves Stripe price ID ADMIN sets STRIPE_PRICE_PRO_MONTHLY in SystemConfig resolvePriceId('PRO','month') reads DB value; env used only as fallback 400 if empty when billing enabled
User at quota User on BASIC, 30/30 semantic_search used reserveUsageOrThrow returns 402 QUOTA_EXCEEDED Fail-open if Redis down (allow + log alert)
Concurrent AI requests Two requests at limit1 Only one succeeds; second gets 402 (atomic Lua) N/A
Admin manual tier override ADMIN sets user to BUSINESS Subscription upserted; AuditLog entry with actor, target, old/new tier Cannot override own tier (existing rule)
Billing disabled BILLING_ENABLED=false in SystemConfig /settings/billing shows disabled state; checkout buttons hidden N/A
suggest-charts usage Successful chart suggestion incrementUsageAsync called once (not broken 4-arg call) N/A

Code Map

  • memento-note/prisma/schema.prisma — add PlanEntitlement model (tier, feature, limitValue; unique on [tier, feature]; null limitValue = unlimited; omit row or sentinel = feature unavailable per tier)
  • memento-note/prisma/migrations/* — additive migration + seed from current TIER_LIMITS
  • memento-note/lib/entitlements.ts — load limits from DB with in-memory cache (TTL ~60s) and hardcoded fallback; export getLimit() async; keep fail-open on Redis errors
  • memento-note/lib/billing/stripe-prices.ts — read price IDs via getConfigValue() with env fallback; remove sole dependence on process.env
  • memento-note/lib/config.ts — add billing keys to ENV_FALLBACKS (BILLING_ENABLED, four STRIPE_PRICE_*)
  • memento-note/app/actions/admin-billing.ts — server actions: getPlanEntitlements, updatePlanEntitlement, updateBillingConfig, getUsageOverview
  • memento-note/app/(admin)/admin/billing/page.tsx — admin UI: tier limits matrix, billing config, usage summary table
  • memento-note/app/(admin)/admin/billing/billing-admin-form.tsx — client form component
  • memento-note/components/admin-sidebar.tsx — nav link /admin/billing
  • memento-note/app/actions/admin.ts — log SUBSCRIPTION_OVERRIDE audit on updateUserSubscription
  • memento-note/lib/audit-log.ts — extend AuditAction with SUBSCRIPTION_OVERRIDE, BILLING_CONFIG_UPDATED, PLAN_ENTITLEMENT_UPDATED
  • memento-note/components/settings/billing-plans.tsx — read billingEnabled from /api/billing/status instead of build-time env only
  • memento-note/app/api/billing/status/route.ts — include billingEnabled from SystemConfig
  • memento-note/app/api/ai/suggest-charts/route.ts — fix usage tracking (use incrementUsageAsync, not broken trackFeatureUsage call)
  • AI routes using checkEntitlementOrThrow + incrementUsageAsync — migrate to reserveUsageOrThrow only (remove duplicate increment on success); include chat route

Tasks & Acceptance

Execution:

  • prisma/schema.prisma + migration — add PlanEntitlement, seed defaults from TIER_LIMITS — DB-backed limits without breaking existing users
  • lib/entitlements.ts — async limit loader with cache + fallback; update canUseFeature, reserveUsageOrThrow, getUserQuotas — single source of truth
  • lib/billing/stripe-prices.ts + lib/config.ts — SystemConfig-backed price IDs and BILLING_ENABLED — admin-editable business config
  • app/actions/admin-billing.ts — CRUD entitlements + billing config + usage overview query (join UsageLog + Redis snapshot) — secure admin API surface
  • app/(admin)/admin/billing/* + components/admin-sidebar.tsx — admin UI section — operator-facing management
  • app/actions/admin.ts + lib/audit-log.ts — audit trail for tier overrides and config changes — support accountability
  • AI routes (grep checkEntitlementOrThrow) + app/api/chat/route.ts — switch to reserveUsageOrThrow; drop redundant incrementUsageAsync on success — fix TOCTOU race
  • app/api/ai/suggest-charts/route.ts — fix broken usage call — restore quota accounting
  • app/api/billing/status/route.ts + components/settings/billing-plans.tsx — runtime billing flag — no redeploy to toggle billing UI
  • locales/en.json + locales/fr.json — i18n keys under admin.billing.* — FR/EN reference labels

Acceptance Criteria:

  • Given an ADMIN on /admin/billing, when they change PRO chat limit and save, then a new entitlement check within 60s reflects the new limit without app restart.
  • Given a BASIC user at quota, when two parallel AI requests arrive, then at most one succeeds and the other returns HTTP 402.
  • Given BILLING_ENABLED is false in SystemConfig, when a user opens /settings/billing, then checkout/upgrade actions are hidden and a clear disabled message is shown.
  • Given an ADMIN changes a user's tier from /admin/users, when the change succeeds, then an AuditLog row records actor, target user, old tier, and new tier.
  • Given Stripe price IDs are set in SystemConfig (env unset), when checkout is initiated, then the correct Stripe price is used.
  • Given Redis is unreachable, when an AI request is made, then the request is allowed (fail-open) and an error is logged server-side.
  • Given STRIPE_SECRET_KEY remains env-only, when inspecting SystemConfig table, then no sk_ or whsec_ values exist.

Design Notes

PlanEntitlement limit encoding: limitValue: null → unlimited; positive integer → monthly request cap; omit row (or explicit -1 sentinel if needed) → feature not available on tier (maps to current undefined in TIER_LIMITS).

Cache invalidation: After admin saves entitlements, call a small invalidateEntitlementCache() so changes apply immediately without waiting for TTL.

Quota migration pattern: Replace await checkEntitlementOrThrow(userId, feature) + incrementUsageAsync(...) with await reserveUsageOrThrow(userId, feature) at request start. Do not increment again on success. For streaming chat, reserve before streamText; no post-finish increment.

Usage dashboard: Show per-tier aggregate from UsageLog (last synced month) plus optional live Redis read for current month top features — degraded state if sync cron stale is acceptable (show syncedAt).

Verification

Commands:

  • cd memento-note && npm run test:unit -- tests/unit/entitlements.test.ts — expected: pass after async limit loader changes
  • cd memento-note && npx tsc --noEmit — expected: no type errors

Manual checks:

  • ADMIN /admin/billing: edit a limit, save, verify /api/usage/current reflects new cap for a test user on that tier.
  • Non-admin GET to admin-billing server actions returns unauthorized.
  • Two rapid AI calls at quota1: only one succeeds.

Spec Change Log

Suggested Review Order

Entitlements & DB limits

Admin console

Stripe & billing UX

Quota hardening

Tests