diff --git a/docs/api/reference.md b/docs/api/reference.md index e214b24..5a05c6d 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -34,9 +34,9 @@ backend, where paths are relative to `/api` unless noted. | Method | Path | Access | Behavior | |---|---|---|---| -| `GET` | `/problems` | Optional user | Tag filter, user status, cursor/limit, max 200 | -| `GET` | `/problems/:slug` | Optional user | Detail/count; editorial only for Admin/accepted solver | -| `GET` | `/problems/:slug/practice` | Public | Active versioned browser-practice cases; `no-store` | +| `GET` | `/problems` | Optional user | Tag filter, user status, cursor/limit, max 200; annotates `accessTier` and `locked` | +| `GET` | `/problems/:slug` | Optional user | Detail/count; editorial only for Admin/accepted solver; enforced paid content returns `402 PLUS_REQUIRED` | +| `GET` | `/problems/:slug/practice` | Optional user | Active versioned browser-practice cases; enforced paid content returns `402 PLUS_REQUIRED`; `no-store` | | `POST` | `/problems` | Admin | Create problem and bounded testcases | | `PUT` | `/problems/:id` | Admin | Optimistic content and atomic-version testcase switch | | `DELETE` | `/problems/:id` | Admin | Delete problem/testcases | @@ -47,6 +47,25 @@ suite because browser execution is a non-adversarial practice mode, not ranked judging. Testcase versions publish by a single Problem pointer switch, so clients see either the old or new set and never a partial edit. +The launch free set is a stable server-owned allowlist of 60 problems. Paid +enforcement is independently controlled by `PAID_ENTITLEMENTS_ENFORCED`; while +it is false, access labels are visible but no problem is blocked. + +## Billing and membership + +| Method | Path | Access | Behavior | +|---|---|---|---| +| `GET` | `/billing/offers` | Public | Four immutable INR offer snapshots and safe runtime flags | +| `GET` | `/billing/summary` | User | Effective Free/Plus/Lumus entitlement and current billing state | +| `POST` | `/billing/checkouts` | User | Idempotent hosted Cashfree subscription/order session; requires Indian phone | +| `POST` | `/billing/subscriptions/:id/cancel` | Owner | Stop future renewals while preserving the paid period | +| `POST` | `/billing/webhooks/cashfree` | Signed provider | Raw-body HMAC, replay/amount/currency checks, fulfillment and full Lumus refund revocation | + +Checkout accepts only an `offerKey`; price, currency and benefits are resolved +server-side. The return page is informational. Only a verified webhook creates +or revokes an entitlement. All billing, checkout, webhook processing and paid +enforcement flags default to false. + ## Execution and submissions | Method | Path | Access | Behavior | diff --git a/docs/architecture/data-model.md b/docs/architecture/data-model.md index e58575c..b34bdf4 100644 --- a/docs/architecture/data-model.md +++ b/docs/architecture/data-model.md @@ -6,6 +6,9 @@ erDiagram USER ||--o{ SESSION : owns USER ||--o{ EVALUATION_JOB : queues USER ||--o{ AUDIT_EVENT : acts + USER ||--o{ ENTITLEMENT_GRANT : receives + USER ||--o{ BILLING_SUBSCRIPTION : owns + USER ||--o{ BILLING_PURCHASE : owns USER }o--o{ CONTEST : registers USER ||--o{ LEADERBOARD : ranks PROBLEM ||--o{ SUBMISSION : receives @@ -63,6 +66,31 @@ erDiagram date revokedAt string replacedBy } + ENTITLEMENT_GRANT { + ObjectId userId + string tier + stringArray benefits + string sourceType + ObjectId sourceId + date startsAt + date endsAt + string status + } + BILLING_SUBSCRIPTION { + ObjectId userId + string offerKey + string providerSubscriptionId + string status + date currentPeriodEnd + boolean cancelAtPeriodEnd + } + BILLING_PURCHASE { + ObjectId userId + string offerKey + string providerOrderId + string status + date capturedAt + } EVALUATION_JOB { ObjectId userId ObjectId submissionId diff --git a/docs/architecture/decisions.md b/docs/architecture/decisions.md index 2aba318..86f8883 100644 --- a/docs/architecture/decisions.md +++ b/docs/architecture/decisions.md @@ -44,6 +44,15 @@ Use short-lived cookie access tokens and rotated, hashed, server-tracked refresh Sessions. Route live browser API traffic through the same-origin Next.js BFF; never persist or return production bearer credentials to browser JavaScript. +### ADR-010 — Provider-neutral billing and internal entitlements + +Use Cashfree as the initial India-first adapter while keeping Katalume's +server-side entitlement ledger as the source of truth for access. Weekly, +monthly, yearly and Lumus lifetime offers are versioned independently of the +provider. Browser callbacks never grant access; signed, replay-safe webhooks +do. Checkout and enforcement use independent safe-default feature flags. See +[Subscription readiness](subscriptions.md). + ## Required before launch ### ADR-008 — Controlled launch scope @@ -57,12 +66,3 @@ traffic only after production-like evidence passes. Promote the same immutable release from staging to production. Production requires explicit approval, post-deploy smoke tests, observable health, and a tested rollback. - -### ADR-010 — Provider-neutral billing and internal entitlements - -Keep billing disabled during the free beta. When commercial activation is -approved, treat the payment provider as the source of truth for money movement -and Katalume's server-side entitlement ledger as the source of truth for access. -All provider calls sit behind an adapter, browser callbacks never grant access, -and webhook processing is signed, idempotent, durable, ordered, and -reconcilable. See [Subscription readiness](subscriptions.md). diff --git a/docs/architecture/subscriptions.md b/docs/architecture/subscriptions.md index fbbdea1..b4da354 100644 --- a/docs/architecture/subscriptions.md +++ b/docs/architecture/subscriptions.md @@ -1,13 +1,42 @@ # Subscription readiness -**Status:** target-state design only -**Runtime state:** disabled -**Pricing state:** undecided -**Payment provider state:** unselected - -Katalume must not display checkout, collect payment details, create mandates, or -grant paid access from a client-side callback until every activation gate in -this document is closed. The public beta remains free. +**Status:** implementation complete; commercial activation pending + +**Runtime state:** disabled by safe-default feature flags + +**Pricing state:** version 1 launch configuration + +**Payment provider state:** Cashfree selected for the India-first adapter + +Katalume may display its membership catalog while checkout is disabled, but it +must not create mandates or orders until every activation gate in this document +is closed. A browser redirect never grants paid access. The production beta +remains fully accessible until paid-entitlement enforcement is separately +enabled. + +## Implemented membership model + +- Free includes a stable, topic-balanced set of 60 problems: 30 Easy, 20 Medium + and 10 Hard. +- Plus includes every current and future problem, Interview Tracks, premium + Progress intelligence, and the premium Profile identity layer. +- Plus is offered weekly, monthly, or yearly. +- Lumus is the one-time lifetime tier and carries the same all-access benefits. +- Competitions remain Coming Soon and are outside the billing gate. +- Learn remains outside this rollout until its content owner completes the + learning experience and the benefit split is reviewed. + +| Offer key | Display name | Price | Collection | +|---|---|---:|---| +| `plus_weekly_in_v1` | Plus Weekly | ₹79 weekly | Cashfree periodic subscription | +| `plus_monthly_in_v1` | Plus Monthly | ₹249 monthly | Cashfree periodic subscription | +| `plus_yearly_in_v1` | Plus Yearly | ₹1,999 yearly | Cashfree periodic subscription | +| `lumus_lifetime_in_v1` | Lumus Lifetime | ₹4,999 once | Cashfree one-time order | + +These are immutable version 1 offer snapshots in code, not permission for the +production account to charge. Changing a live price requires a new offer key. +The amounts and tax display still require owner and professional review before +activation. ## Design goals @@ -19,11 +48,10 @@ this document is closed. The public beta remains free. later without rewriting product authorization. - Preserve a useful free tier and avoid locking learning history behind payment. -## Explicit non-goals for this phase +## Explicit non-goals for activation -- No prices, discounts, trials, or paid feature limits are approved. -- No provider SDK, API key, webhook secret, checkout route, or billing UI is - added to production. +- No discount, trial, coupon, proration, upgrade/downgrade, or family plan is + included in version 1. - No payment-method data is stored by Katalume. - No claim is made that lifetime access means the service must operate forever. - This document is engineering readiness, not tax or legal advice. @@ -64,17 +92,20 @@ unit-economics review. 7. Manual support grants must be separate, attributed, expiring where appropriate, and auditable. -## Target data model +## Production data model All identifiers below are internal opaque IDs unless prefixed with -`provider`. +`provider`. The shipped version implements the customer, subscription, +purchase, webhook-event, and entitlement-grant subset. Payment/refund ledgers, +tax profiles, dead-letter automation, and support reconciliation remain +activation gates rather than claims about the current code. ### `BillingCustomer` | Field | Purpose | |---|---| | `userId` | Unique Katalume user | -| `provider` | Adapter key, for example `razorpay` | +| `provider` | Adapter key, initially `cashfree` | | `providerCustomerId` | Encrypted or access-restricted external reference | | `billingEmail` | Invoice/contact address; separate from login changes | | `taxProfile` | Optional legal name, country/state, postal code, GSTIN after validation | @@ -176,36 +207,28 @@ during a short provider outage. ## Backend module boundary -Future billing code should live behind a provider-neutral boundary: +Billing code lives behind this provider-neutral boundary: ```text src/billing/ - billing.service - entitlement.service - offer.repository - webhook.service - reconciliation.service + billing.service.js + entitlement.service.js providers/ - provider.interface - razorpay.adapter + cashfree.adapter.js ``` -The adapter contract should cover: +The current adapter covers: ```text -createCustomer createCheckout -fetchSubscription cancelSubscription -createRefund verifyWebhook -normalizeWebhook ``` Controllers must not call a provider SDK directly. Provider objects must not leak into user or authorization models. -## Planned API surface +## Implemented API surface All mutating endpoints require an authenticated user, CSRF protection where applicable, distributed rate limits, and an idempotency key. @@ -215,10 +238,8 @@ applicable, distributed rate limits, and an idempotency key. | `GET /api/billing/offers` | Return active, server-approved offer snapshots | | `GET /api/billing/summary` | Return the user's normalized billing and entitlement state | | `POST /api/billing/checkouts` | Create a provider-hosted checkout/mandate flow | -| `POST /api/billing/subscriptions/:id/cancel` | Cancel now or at period end | -| `POST /api/billing/subscriptions/:id/resume` | Resume if the provider permits it | -| `POST /api/billing/webhooks/:provider` | Receive signed provider events; no user auth | -| `POST /api/admin/billing/reconcile/:id` | Restricted, audited repair | +| `POST /api/billing/subscriptions/:id/cancel` | Cancel future renewals; retain the already-paid access window | +| `POST /api/billing/webhooks/cashfree` | Receive signed Cashfree events; no user auth | The frontend should learn access through `/api/billing/summary`; it must not infer paid status from query parameters, local storage, or a provider response. @@ -230,7 +251,6 @@ sequenceDiagram participant U as User browser participant K as Katalume API participant P as Payment provider - participant W as Billing worker U->>K: Create checkout (offerKey + idempotency key) K->>K: Validate active offer and snapshot terms @@ -240,10 +260,10 @@ sequenceDiagram U->>P: Complete provider-controlled payment flow P-->>U: Return to informational status page P->>K: Signed webhook - K->>K: Verify signature and persist event once - K-->>P: 2xx after durable receipt - W->>K: Normalize event and update billing state atomically - W->>K: Create/revoke entitlement grant + K->>K: Verify raw-body signature and acquire event once + K->>K: Validate amount/currency and update billing state + K->>K: Create or revoke entitlement grant + K-->>P: 2xx after processing U->>K: Fetch billing summary K-->>U: Verified entitlement ``` @@ -255,12 +275,12 @@ Webhook processing rules: side effects. 3. Persist the unique event and payload hash before acknowledging it. 4. Return success for an identical replay. -5. Process asynchronously with bounded retries and dead-letter alerting. -6. Lock the billing aggregate and reject stale state transitions using provider - occurrence time plus provider sequence/version when available. -7. Apply billing state, payment state, and entitlement changes in one database - transaction where supported. -8. Reconcile uncertain events against the provider API. +5. Acquire the event with a compare-and-set processing lease so concurrent + deliveries do not execute twice; a stale lease may be retried. +6. Reject stale subscription-state transitions using provider occurrence time. +7. Validate the server-owned amount and currency before every grant. +8. Before live activation, add scheduled provider reconciliation and alerts for + uncertain multi-write failures. ## India-first payment requirements @@ -302,6 +322,8 @@ transaction; do not recompute old invoices from current settings. - Enforce server-side offer lookup; reject client-supplied amounts, currency, tier, tax, or provider plan IDs. - Bind checkout ownership to the authenticated Katalume user. +- Cancel renewable mandates before account deletion; anonymize billing contact + data retained for financial/legal records and remove product entitlements. - Tokenize through the provider and keep Katalume out of raw card/UPI data. - Redact provider payloads, email, phone, GSTIN, addresses, and failure detail from ordinary logs and error reporting. @@ -327,16 +349,19 @@ paid entitlements, and enabling entitlement enforcement must not expose checkout. Unknown or missing configuration fails closed for new purchases while preserving already-verified access. -Provider-specific configuration is added only after selection: +Cashfree-specific configuration: ```text -BILLING__KEY_ID -BILLING__KEY_SECRET -BILLING__WEBHOOK_SECRET -BILLING__ACCOUNT_ID +CASHFREE_CLIENT_ID +CASHFREE_CLIENT_SECRET +BILLING_WEBHOOK_URL +BILLING_ENVIRONMENT=sandbox|production ``` -Do not create these variables or placeholder secrets in production yet. +Secrets belong only in Render's secret store. `BILLING_WEBHOOK_URL` must be the +direct public backend URL ending in `/api/billing/webhooks/cashfree`; do not +route it through the frontend BFF because signature verification uses the exact +raw request bytes and Cashfree headers. ## Testing strategy @@ -354,11 +379,11 @@ Do not create these variables or placeholder secrets in production yet. - checkout request retry with one idempotency key; - duplicate/out-of-order webhook delivery; -- event persisted but worker interrupted before state update; +- event acquired but the API interrupted before state update; - provider timeout after an unknown checkout result; - successful payment with delayed webhook; - failed renewal, recovery, cancellation, refund, chargeback, and dispute; -- database transaction failure at each write boundary; +- database failure at each write boundary and stale processing-lease recovery; - secret rotation and test/live environment isolation. ### Production-like proof @@ -403,17 +428,17 @@ Never overwrite provider A identifiers with provider B identifiers. ## Activation gates -- [ ] Free-versus-paid benefits approved from user research -- [ ] Weekly/monthly/yearly/lifetime prices and tax display approved +- [x] Free-versus-paid benefits implemented behind independent enforcement +- [ ] Weekly/monthly/yearly/lifetime prices and tax display approved for live sale - [ ] Lifetime terms and business liability approved -- [ ] Provider selected after capability, onboarding, support, and total-cost review +- [x] Cashfree selected as the initial India-first adapter - [ ] Legal entity, bank/settlement, KYC, GST, invoices, terms, privacy, refunds, and cancellation approved -- [ ] Backend models, adapter, APIs, workers, migrations, and indexes implemented -- [ ] Frontend pricing, checkout, manage-billing, invoice, and recovery UX implemented -- [ ] Webhook signature, idempotency, ordering, retry, and reconciliation tests green +- [x] Backend models, adapter, APIs, access checks, and indexes implemented +- [x] Frontend pricing, hosted checkout, membership, cancellation, and recovery UX implemented +- [x] Webhook signature, replay, amount verification, subscription, and lifetime tests green - [ ] Test/live secrets and environments isolated - [ ] Support and finance runbooks rehearsed -- [ ] Accessibility, mobile, localization, analytics-consent, and security review passed +- [ ] Accessibility, mobile, localization, analytics-consent, and security review passed in staging - [ ] Staged test-mode and owner-only live canary passed - [ ] `BILLING_ENABLED` separately approved for production @@ -424,8 +449,10 @@ flags remain false. - [NPCI: UPI AutoPay](https://www.npci.org.in/product/autopay) - [Reserve Bank of India: e-mandate framework index/circular](https://www.rbi.org.in/scripts/bs_circularindexdisplay.aspx/Scripts/BS_CircularIndexDisplay.aspx?Id=12722) -- [Razorpay: subscription webhook events](https://razorpay.com/docs/payments/subscriptions/subscribe-to-webhooks/) -- [Razorpay: webhook processing overview](https://razorpay.com/docs/webhooks/) +- [Cashfree: subscription FAQ](https://www.cashfree.com/docs/payments/subscription/faq) +- [Cashfree: webhook signatures](https://www.cashfree.com/docs/payments/subscription/webhook-signature) +- [Cashfree: subscription API](https://www.cashfree.com/docs/api-reference/payments/latest/subscription/overview) +- [Cashfree: hosted web checkout](https://www.cashfree.com/docs/payments/online/web/redirect) - [GST portal: e-invoicing glossary](https://tutorial.gst.gov.in/downloads/news/pamphlet_e_invoicing_glossary_updated_17_08_2023_approved_final.pdf) These links are implementation inputs, not permanent constants. Re-check them diff --git a/docs/changelog.md b/docs/changelog.md index f6df4d9..d418ea6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,14 +2,17 @@ ## 2026-07-19 -- Added the disabled-by-default, provider-neutral subscription architecture for - weekly, monthly, yearly, and lifetime INR offers. -- Defined immutable offer versions, normalized subscription/payment state, - internal entitlement grants, signed durable webhooks, reconciliation, - India-first payment requirements, provider migration, tests, observability, - and activation gates. -- Kept pricing, provider selection, checkout, secrets, and paid entitlement - enforcement explicitly unimplemented for the free beta. +- Implemented a disabled-by-default, provider-neutral membership system with + weekly, monthly, yearly, and Lumus lifetime INR offers. +- Added Cashfree hosted checkout adapters, server-owned immutable prices, + signed/replay-safe webhooks, internal entitlement grants, cancellation, + full-lifetime-refund revocation, privacy export/deletion handling, indexes, + tests, and a staged activation runbook. +- Added a stable 60-problem Free set, Plus catalog labels and gates, + Plus-exclusive Interview Tracks shell, membership management, and enhanced + Progress/Profile panels. Learn and Competitions remain unchanged. +- Kept checkout, webhook processing, and paid enforcement disabled until + merchant onboarding, legal/tax approval, sandbox proof, and an owner canary. ## 2026-07-16 diff --git a/docs/operations/billing-activation.md b/docs/operations/billing-activation.md new file mode 100644 index 0000000..1e5a707 --- /dev/null +++ b/docs/operations/billing-activation.md @@ -0,0 +1,103 @@ +# Billing activation runbook + +The code and UI are deployable with all billing flags disabled. Do not combine +deployment with commercial activation. + +## Safe deployed state + +```text +BILLING_ENABLED=false +CHECKOUT_ENABLED=false +BILLING_WEBHOOK_PROCESSING_ENABLED=false +PAID_ENTITLEMENTS_ENFORCED=false +BILLING_PROVIDER=disabled +BILLING_ENVIRONMENT=sandbox +``` + +In this state the membership catalog may be visible, no Cashfree request is +made, and all problems remain accessible. + +## Owner prerequisites + +1. Complete Cashfree merchant, KYC, bank and settlement onboarding. +2. Obtain professional decisions for GST, place of supply, invoices, refunds, + cancellation wording, consumer terms, privacy and lifetime liability. +3. Approve version 1 prices and tax-inclusive or tax-exclusive presentation. +4. Publish Terms, Privacy, Refund and Cancellation pages linked from checkout. +5. Create separate Cashfree sandbox and production credentials. + +Payment processing is not free: provider transaction and applicable tax fees +must be included in unit economics even though the disabled implementation has +no fixed billing infrastructure charge. + +## Sandbox activation + +Set backend secrets without printing them: + +```text +BILLING_PROVIDER=cashfree +BILLING_ENVIRONMENT=sandbox +CASHFREE_CLIENT_ID= +CASHFREE_CLIENT_SECRET= +BILLING_WEBHOOK_URL=https://katalume-api.onrender.com/api/billing/webhooks/cashfree +``` + +Enable in this order, with a redeploy and smoke test after each step: + +1. `BILLING_ENABLED=true` +2. `BILLING_WEBHOOK_PROCESSING_ENABLED=true` +3. `CHECKOUT_ENABLED=true` +4. Keep `PAID_ENTITLEMENTS_ENFORCED=false`. + +Test all four offers. Verify: + +- a checkout retry with the same idempotency key creates one provider resource; +- a browser return does not change access; +- an invalid signature, stale timestamp, wrong amount and wrong currency grant + nothing; +- a valid recurring `CHARGE` creates a bounded Plus grant; +- a valid Lumus payment creates one non-expiring grant; +- a verified full Lumus refund revokes that grant; +- duplicate webhooks produce one event and one grant; +- cancellation stops renewal but preserves paid access through period end; +- account deletion first cancels any renewable mandate, then anonymizes retained + billing records and removes product entitlements; +- no payment credentials or unredacted provider payload appear in logs. + +## Staging enforcement + +After sandbox payment and cancellation pass, set +`PAID_ENTITLEMENTS_ENFORCED=true` in staging only. Verify: + +- the problem list labels free and Plus problems; +- all 60 free problems open and run; +- a free user cannot fetch a paid problem body or its practice testcases; +- Plus and Lumus users can open all problems; +- Interview Tracks is blocked for Free and remains Coming Soon for paid users; +- Learn and Competitions remain unchanged; +- expired grants return to Free without losing history. + +## Production canary + +1. Switch to isolated production Cashfree keys and + `BILLING_ENVIRONMENT=production`. +2. Keep paid enforcement off. +3. Enable billing, webhook processing and checkout. +4. Run one low-value owner-only live recurring purchase and one Lumus purchase. +5. Verify settlement, entitlement, cancellation and refund end to end. +6. Revoke or rotate test credentials and confirm sandbox events cannot reach + production. +7. Enable paid enforcement only after the owner signs off and support is ready. + +## Rollback + +Disable in this order: + +1. `CHECKOUT_ENABLED=false` +2. `PAID_ENTITLEMENTS_ENFORCED=false` +3. Keep webhook processing on while active subscriptions can still produce + money events. + +Do not disable webhook processing merely because checkout is off. Existing +mandates may renew until cancelled. If the provider is unhealthy, stop new +checkout first and preserve the last verified, unexpired entitlements. diff --git a/docs/product/overview.md b/docs/product/overview.md index b5297d5..a4e642c 100644 --- a/docs/product/overview.md +++ b/docs/product/overview.md @@ -51,6 +51,7 @@ The name combines **kata**, deliberate practice that forges mastery, with The current public release is a practice beta, not an adversarial ranked judge: browser-delivered tests can be inspected and local history is device-specific. -Ranked contests, billing, team/organization administration, email verification, -password recovery, and plagiarism detection are not complete production -capabilities. +Billing code is deployed dark and cannot charge until its commercial activation +gates are completed. Ranked contests, team/organization administration, email +verification, password recovery, and plagiarism detection are not complete +production capabilities. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index e0a39ef..22b778c 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -39,6 +39,24 @@ or non-HTTPS URLs. | `GOOGLE_CLIENT_ID/SECRET` | Required for Google | Provider credentials | | `GITHUB_CLIENT_ID/SECRET` | Both or neither | Provider stays hidden if incomplete | +## Backend billing profile + +Billing deploys dark. The defaults are: + +| Variable | Safe value | Purpose | +|---|---:|---| +| `BILLING_ENABLED` | `false` | Master billing integration gate | +| `CHECKOUT_ENABLED` | `false` | Allows new provider orders and mandates | +| `BILLING_WEBHOOK_PROCESSING_ENABLED` | `false` | Lets signed money events update the ledger | +| `PAID_ENTITLEMENTS_ENFORCED` | `false` | Applies free-versus-paid product gates | +| `BILLING_PROVIDER` | `disabled` | Set to `cashfree` only during activation | +| `BILLING_ENVIRONMENT` | `sandbox` | Cashfree environment isolation | +| `BILLING_WEBHOOK_URL` | Blank | Direct backend webhook URL | +| `CASHFREE_CLIENT_ID/SECRET` | Blank secret | Server-side authentication; the Cashfree PG secret also verifies raw-body webhook HMACs | + +See [Billing activation](../operations/billing-activation.md) for the activation +and rollback order. + The Upstash REST token is not a Redis TCP password. Copy the TCP `REDIS_URL` from Upstash and validate it with `PING` without printing the value. diff --git a/mkdocs.yml b/mkdocs.yml index ef39ad9..61aba7f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,6 +88,7 @@ nav: - Observability: operations/observability.md - Security: operations/security.md - Runbooks: operations/runbooks.md + - Billing activation: operations/billing-activation.md - Launch: - Production readiness: launch/readiness.md - July 20 plan: launch/july-20-plan.md