[
  {
    "file": "0001_init.sql",
    "summary": "PeltyCMS initial schema: identity, sessions, RBAC, audit, settings."
  },
  {
    "file": "0002_extensions.sql",
    "summary": "Plugins, payment gateways, webhook deduplication, minimal payments ledger."
  },
  {
    "file": "0003_perf_indexes.sql",
    "summary": "Match the admin user list ordering exactly (created_at DESC, id) so the page is an index scan with no sort."
  },
  {
    "file": "0004_billing.sql",
    "summary": "Milestone 2: billing core. All money is NUMERIC(19,4) + ISO currency; the currency's exponent decides rounding. Never floats."
  },
  {
    "file": "0005_dashboard_indexes.sql",
    "summary": "Partial indexes backing the dashboard aggregates."
  },
  {
    "file": "0006_clear_dummy_bank_text.sql",
    "summary": "0002 seeded a placeholder IBAN into the manual gateway. Bank details now live in system_settings (billing.bank_transfer); drop the placeholder only if the operator never changed it."
  },
  {
    "file": "0007_email.sql",
    "summary": "Email: operator-editable templates, password resets, email verification."
  },
  {
    "file": "0008_mfa.sql",
    "summary": "TOTP two-factor auth. Secrets are AES-256-GCM sealed by the app (PELTY_ENCRYPTION_KEY); backup codes and challenge tokens are SHA-256 only."
  },
  {
    "file": "0009_avatars.sql",
    "summary": "Profile pictures, re-encoded server-side to PNG (256px and 64px squares)."
  },
  {
    "file": "0010_tickets.sql",
    "summary": "Support tickets."
  },
  {
    "file": "0011_ticket_attachments.sql",
    "summary": "Ticket attachments. Uploaded first (pending, owned by the uploader), then linked to a message when the reply/ticket is posted. Bytes live in the DB so there is no filesystem path handling; served only as downloads."
  },
  {
    "file": "0030_addons_custom_fields.sql",
    "summary": "Product addons (sold alongside a product) and custom order fields."
  },
  {
    "file": "0040_coupons.sql",
    "summary": "Coupons / promo codes. Discounts are applied as a negative invoice line and, for recurring coupons, persisted on the service's recurring amount."
  },
  {
    "file": "0050_affiliates.sql",
    "summary": "Affiliates: referral codes, attribution by tracking cookie at signup, commissions accrued (exact decimals) when a referred customer's invoice is paid, and payout requests against the available balance."
  },
  {
    "file": "0051_quotes.sql",
    "summary": "Quotes / proposals. Accepting a sent, unexpired quote turns it into a finalized invoice exactly once (quote row locked, invoice_id set)."
  },
  {
    "file": "0052_downloads.sql",
    "summary": "Downloads area: files stored in the database (no filesystem paths), gated by owning an active service of one of the linked products. No linked products = available to every signed-in customer."
  },
  {
    "file": "0090_content.sql",
    "summary": "Knowledge base and announcements. Content is per-locale; `locale` NULL means \"all languages\". Only published rows are visible publicly."
  },
  {
    "file": "0091_client_groups.sql",
    "summary": "Client groups (WHMCS \"Client Groups\"): label colour, a percentage discount applied by pricing code that opts in, and an optional \"exempt from late fees\" flag."
  },
  {
    "file": "0092_api_keys.sql",
    "summary": "Personal API keys. Only the SHA-256 of the secret is stored; `prefix` is the public, non-secret part shown in the UI. A key's effective permissions are its scopes intersected with its owner's current permissions."
  },
  {
    "file": "0095_import_map.sql",
    "summary": "Idempotent importer bookkeeping: one row per imported source record."
  },
  {
    "file": "0096_tax.sql",
    "summary": "Tax engine: rules per country/region, inclusive/exclusive pricing (global setting `billing.tax`), compound taxes, EU VAT reverse charge."
  },
  {
    "file": "0100_provisioning.sql",
    "summary": "Provisioning: panel servers, product → server binding, per-service remote state."
  },
  {
    "file": "0101_api_keys_admin.sql",
    "summary": "Admins can list and revoke any user's API keys."
  },
  {
    "file": "0102_reports.sql",
    "summary": "Financial reports (revenue, MRR, churn). Admin and staff can read."
  },
  {
    "file": "0103_gdpr.sql",
    "summary": "GDPR: data-subject erasure requests (export is on demand, nothing stored)."
  },
  {
    "file": "0104_fraud.sql",
    "summary": "Fraud screening results for client orders (built-in rules + plugin hooks)."
  },
  {
    "file": "0110_service_changes.sql",
    "summary": "Plan changes (upgrade/downgrade with proration) and cancellation requests."
  },
  {
    "file": "0120_support_suite.sql",
    "summary": "Support suite: canned replies, tags, ratings, SLA first response, merge, escalation rules and inbound e-mail piping."
  },
  {
    "file": "0121_overdue.sql",
    "summary": "Overdue automation: one row per (invoice, action) so reminders, late fees and suspensions happen exactly once even if the daily job runs twice."
  },
  {
    "file": "0122_credit.sql",
    "summary": "Customer credit balance as an append-only ledger (per currency). The balance is the sum of entries; it never goes below zero (checked under a row lock on the customer)."
  },
  {
    "file": "0130_domains.sql",
    "summary": "Domains: TLD price list, registered/transferred domains and renewals."
  },
  {
    "file": "0160_config_option_types.sql",
    "summary": "Configurable option types (WHMCS: dropdown, radio, quantity, yes/no). quantity: the single choice's price is per unit; yesno: the single choice is \"yes\" (absent = no)."
  },
  {
    "file": "0161_network_issues.sql",
    "summary": "Network / service status issues (WHMCS \"Network Issues\"), public page + admin."
  },
  {
    "file": "0162_client_notes_fields_todos.sql",
    "summary": "Staff notes on clients (multiple, sticky), custom client fields and the admin to-do list."
  },
  {
    "file": "0163_billable_items.sql",
    "summary": "Per-product invoice generation lead time (NULL = global default) and billable items: one-off or recurring charges billed to a client."
  },
  {
    "file": "0170_plugin_webhooks.sql",
    "summary": "Durable, HMAC-signed event delivery to external plugin webhook URLs."
  },
  {
    "file": "0171_usage_billing.sql",
    "summary": "Metered / usage billing: per-product metrics, raw usage records grouped into per-service periods that are invoiced when the period (cycle) ends."
  },
  {
    "file": "0180_login_lockout.sql",
    "summary": "Per-account login backoff: after LOCK_AFTER consecutive failures the account is locked for an exponentially growing window (capped)."
  },
  {
    "file": "0181_refunds.sql",
    "summary": "Refunds against individual payments (partial or full), executed through the payment gateway. Each successful refund issues a credit note."
  },
  {
    "file": "0185_currency_rates.sql",
    "summary": "Exchange rates (relative to the default currency, which has rate 1), display prefix/suffix and a single default currency."
  },
  {
    "file": "0186_staff_roles.sql",
    "summary": "Staff role editor: builtin roles cannot be deleted/renamed; admin keeps all permissions."
  },
  {
    "file": "0187_mail_campaigns.sql",
    "summary": "Mass mail campaigns to client segments, sent by the job queue."
  },
  {
    "file": "0190_sso_tokens.sql",
    "summary": "One-time client SSO tokens. The token itself is signed (HMAC under the server API key) and never stored; only its nonce, to burn it on use."
  },
  {
    "file": "0191_bundles.sql",
    "summary": "Product bundles (fixed set of products at a cycle, % off the first invoice) and per-product upsell / cross-sell recommendations."
  },
  {
    "file": "0192_ticket_statuses.sql",
    "summary": "Custom ticket statuses. Built-in ones keep their workflow meaning (replies move tickets between open/answered/customer_reply/closed); custom ones are set manually by staff."
  },
  {
    "file": "0193_ticket_rated_at.sql",
    "summary": "When a customer rated a ticket (satisfaction report period filter)."
  },
  {
    "file": "0195_gateway_deliveries.sql",
    "summary": "Webhook delivery log per gateway (admin troubleshooting). Bodies are not stored (may contain personal data); only outcome metadata."
  },
  {
    "file": "0200_storefront.sql",
    "summary": "Public storefront carts (keyed by an unguessable client token), customer contacts / sub-accounts, email preferences and admin order notes."
  },
  {
    "file": "0201_autopay.sql",
    "summary": "Saved payment methods (provider tokens only, never card data), auto-pay opt-in per customer and the off-session charge attempts with retries."
  },
  {
    "file": "0202_invoice_tools_order_approval.sql",
    "summary": "Pre-due invoice reminders (one mail per invoice and threshold)."
  },
  {
    "file": "0210_legal_entities.sql",
    "summary": "Legal entities (selling companies): per-entity bank details, footer/terms, due-date terms and per-year gapless numbering. Without any entity the legacy global INV-/CN- sequence is used unchanged."
  },
  {
    "file": "0211_gateway_rules.sql",
    "summary": "Per-gateway processing fees, amount limits and currency restrictions."
  },
  {
    "file": "0220_addons.sql",
    "summary": "Addon registry: provisioning modules, payment gateways, themes and plugins must be installed (and enabled) before they can run. See docs/ADDONS.md."
  },
  {
    "file": "0221_site_pages.sql",
    "summary": "Public website CMS pages built from declarative blocks (docs/DESIGN_LAGOM_STYLE.md)."
  },
  {
    "file": "0222_site_legal_pages.sql",
    "summary": "Default pages linked from the public footer (Company / Legal). Admins edit them in Website > Pages."
  },
  {
    "file": "0240_admin_tools.sql",
    "summary": "Admin productivity: personal \"My Notes\" and outbound staff notification rules (Slack / Discord / Teams / generic webhook), WHMCS \"Configure Notifications\"."
  },
  {
    "file": "0247_billing_wave2.sql",
    "summary": "Billing wave 2: full ISO 4217 currency list, currency display format, automatic exchange-rate updates (configurable source + markup), invoice merge/split bookkeeping and gift cards / prepaid credit codes."
  },
  {
    "file": "0261_admin_ops.sql",
    "summary": "Admin ops: per-staff dashboard/quick-link prefs, DB backups, custom code permission."
  },
  {
    "file": "0262_campaign_schedule.sql",
    "summary": "Mass mail scheduling: a campaign can wait in 'scheduled' until scheduled_at."
  },
  {
    "file": "0263_affiliate_extras.sql",
    "summary": "Affiliate extras (WHMCS parity): per-product commission overrides, referral count tiers and promotional materials. Product-rated invoice lines replace their share of the base commission."
  },
  {
    "file": "0265_registrar_addons_domains_ext.sql",
    "summary": "Registrars become addons (kind 'registrar'); domain pricing gets grace / redemption fees and add-on prices; DNS records and domain lifecycle states."
  },
  {
    "file": "0266_support_ext.sql",
    "summary": "Support extensions (WHMCS parity): department auto-reply and mailbox piping (IMAP/POP3), spam control, canned categories, time tracking, KB tags and contact form messages."
  },
  {
    "file": "0270_security_suite.sql",
    "summary": "Security / accounts suite: captcha nonces, social login (OAuth2/OIDC), IP rules, admin impersonation, WHMCS-compatible API credentials, versioned terms of service, client close/merge."
  },
  {
    "file": "0271_refund_to_credit.sql",
    "summary": "Refund a gateway payment to the customer's account credit instead of back through the gateway (WHMCS \"Add to client's credit balance\")."
  },
  {
    "file": "0272_add_funds.sql",
    "summary": "Add funds: the client pays an invoice and the amount lands in their credit balance exactly once (trigger on the paid transition, same transaction)."
  },
  {
    "file": "0273_client_group_rules.sql",
    "summary": "Automatic client-group assignment rules. Evaluated in priority order; the first matching rule decides the group. Applied on demand by an admin (dry-run first) and only to customers without a group unless overwrite."
  },
  {
    "file": "0275_ticket_watchers.sql",
    "summary": "Extra staff who follow a ticket and get its notifications."
  },
  {
    "file": "0276_promos_free_domain_order_forms.sql",
    "summary": "Promotions: addons/domains scope, recurring for N cycles, per-client/group."
  },
  {
    "file": "0277_subaccount_perms.sql",
    "summary": "Sub-account permissions gain \"orders\" and \"domains\" (WHMCS Users & Accounts)."
  },
  {
    "file": "0278_notify_channels_webhooks.sql",
    "summary": "Telegram, SMS (HTTP gateway or email-to-SMS) notification channels, HMAC signed generic webhooks with a per-attempt delivery log."
  },
  {
    "file": "0279_server_groups.sql",
    "summary": "Server groups: products bound to a group get a server picked automatically (per module, enabled, under max_services) by the group's strategy."
  },
  {
    "file": "0280_product_stock.sql",
    "summary": "Product stock control: optional quantity limit with optional backorders."
  },
  {
    "file": "0281_module_checkout_fields.sql",
    "summary": "Checkout fields declared by a product's provisioning module are synced into product_custom_fields and tagged with the module's field key."
  },
  {
    "file": "0285_oauth_provider.sql",
    "summary": "PeltyCMS as an OAuth 2.0 / OpenID Connect provider."
  },
  {
    "file": "0286_live_chat.sql",
    "summary": "Simple live chat: anonymous visitor sessions (bearer token, hashed) and staff replies, polled by both sides."
  },
  {
    "file": "0287_debit_notes.sql",
    "summary": "Debit notes: an extra charge issued against an existing invoice (the counterpart of a credit note). Linked via credit_for (= \"related invoice\")."
  },
  {
    "file": "0288_module_log_health.sql",
    "summary": "Server health (last connection test) and a log of every module call."
  },
  {
    "file": "0289_saml.sql",
    "summary": "SAML 2.0 Service Provider: identity providers, pending AuthnRequests, and the assertion replay cache."
  },
  {
    "file": "0290_addon_kv.sql",
    "summary": "Addon-private key/value store for WASM addons (host `kv-*` imports). Values are sealed with the app encryption key when one is configured."
  },
  {
    "file": "0291_admin_security.sql",
    "summary": "Admin account security: step-up re-authentication, password age, login alerts."
  },
  {
    "file": "0292_fk_indexes.sql",
    "summary": "Indexes on foreign keys that are filtered by or checked on delete (ON DELETE RESTRICT / CASCADE scans) on the customer, invoice, order, ticket and session paths. Found with a pg_constraint/pg_index scan; see docs/PERFORMANCE.md."
  },
  {
    "file": "0293_email_log.sql",
    "summary": "WHMCS \"Email Message Log\": every transactional mail the worker delivered (or failed to), viewable per client and resendable by staff."
  },
  {
    "file": "0294_card_expiry_banned_emails.sql",
    "summary": "WHMCS \"Credit Card Expiry Notices\": one notice per saved card."
  },
  {
    "file": "0295_fixed_term.sql",
    "summary": "Fixed-term products: terminate a service N days after activation (WHMCS \"Auto Terminate\")."
  },
  {
    "file": "0296_ssl_checks.sql",
    "summary": "SSL certificate monitoring for client domains (WHMCS \"SSL Sync\" / SSL status in client area)."
  },
  {
    "file": "0297_ticket_message_edit.sql",
    "summary": "Staff can edit or delete ticket replies (WHMCS UpdateTicketReply / DeleteTicketReply)."
  },
  {
    "file": "0298_proforma_invoices.sql",
    "summary": "Proforma invoices: an issued invoice may stay unnumbered until it is paid."
  },
  {
    "file": "0299_staff_noticeboard.sql",
    "summary": "Staff noticeboard (WHMCS \"Staff Noticeboard\" addon): short notices shown to all staff."
  },
  {
    "file": "0300_security_questions.sql",
    "summary": "WHMCS security questions: staff-defined questions; each user may store one hashed answer, which staff can check when verifying a caller by phone."
  },
  {
    "file": "0301_link_tracking.sql",
    "summary": "WHMCS-style link tracking: short redirect links that count clicks."
  },
  {
    "file": "0302_payment_reversals.sql",
    "summary": "Payment reversals (chargebacks): money already taken back by the bank or gateway. Settled like a refund (credit note, payment/invoice status) but without calling the gateway."
  },
  {
    "file": "0303_inactive_clients.sql",
    "summary": "Last login, kept even after sessions are purged; used by the inactive-client retention job."
  },
  {
    "file": "0304_projects.sql",
    "summary": "Project management (WHMCS Project Management addon): projects linked to a client, tickets and invoices, with tasks and time tracking billed as invoice lines."
  },
  {
    "file": "0305_domain_pricing_extras.sql",
    "summary": "Registrar cost sync + margin rules, premium domains, per-TLD custom fields."
  },
  {
    "file": "0306_overage_import.sql",
    "summary": "Overage billing from server-module usage + server account import."
  },
  {
    "file": "0307_kyc.sql",
    "summary": "KYC: identity documents uploaded by customers, reviewed by staff."
  },
  {
    "file": "0308_api_gaps.sql",
    "summary": "WHMCS API gaps: UpdateTransaction (staff note on a payment)."
  },
  {
    "file": "0309_vendor_catalog.sql",
    "summary": "MarketConnect-style vendor resale: catalog synced from a vendor server."
  },
  {
    "file": "0310_passkeys.sql",
    "summary": "Passkeys (WebAuthn). The credential is the serialized webauthn-rs Passkey (public key + counter only; no secret material)."
  },
  {
    "file": "0311_passkey_discoverable.sql",
    "summary": "Usernameless (discoverable credential) passkey sign-in ceremonies."
  },
  {
    "file": "0312_tld_field_key_case.sql",
    "summary": "Registrar parameter names are case-sensitive (RegistrantNexus, X-CA-LEGALTYPE, contactRegistrant.nexusCategory): keep the key exactly as typed, but two keys differing only in case are still one field."
  },
  {
    "file": "0313_registrant_verification.sql",
    "summary": "ICANN Registrar Accreditation Agreement (WHOIS accuracy program): the registrant's e-mail must be verified within 15 days of a registration, transfer or registrant e-mail change. One row per domain (latest request)."
  },
  {
    "file": "0314_translation_overrides.sql",
    "summary": "Translation editor: admin overrides on top of the shipped locale files."
  },
  {
    "file": "0315_duo.sql",
    "summary": "Duo Universal Prompt: OIDC state between redirect and callback."
  }
]