Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)
    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Firebase Integration

    Use the Founder / Developer tabs in the docs sidebar to filter this page for your role. audience frontmatter controls content filtering within the page. Sidebar article visibility is controlled separately by lib/docs/audience-curated-docs.ts.

    Ring Platform uses Firebase in two separate paths — a server-side Admin SDK v14 (for FCM push, Firestore, Auth, Storage, App Check, and Remote Config) and a client-side browser SDK (for FCM push token registration, Auth state, Firestore real-time, and AI Logic). The two paths use different credential strategies and different SDK versions.

    This page covers the full Firebase 14 Enterprise stack in firebase-full ring-db mode: pipeline operations, change streams, text/geo search, FCM Admin HTTP v1 bridge, SSOT atomic writes, AI Logic, Firebase Hosting config, React 19 cache()-native helpers, and ProcessConductor scheduled jobs (Cloud Scheduler → the same /api/cron/* URLs as Vercel Cron).

    Backend modes

    Push notifications (FCM)

    Environment variables

    Authentication

    Local setup

    Why firebase-full mode?

    firebase-full is a first-class ring-db mode (elevated from prototyping on 2026-06-30). In this mode, all application data lives in Firebase Firestore, push notifications use Firebase Cloud Messaging, file uploads can use Firebase Storage, and the Firebase Hosting service can serve your Ring clone with a single firebase deploy command.

    What this means for your clone

    CapabilityWhat you get
    No PostgreSQL neededAll data lives in Firestore — no database server to manage, no connection pooling, no SSL certs
    Unlimited scaleFirebase manages read, write, and concurrency limits — your clone auto-scales
    Push notificationsFCM web push with React hooks, service worker, and Server Action token upsert (see Push notifications FCM)
    AI-powered featuresFirebase AI Logic for Gemini 2.5/3.x text generation, chat, and embedding — directly from the SDK

    Server-side files

    FilePurposeCredential strategy
    lib/firebase-admin.server.tsFirebase Admin SDK v14 singleton — FCM, Auth, Firestore, Storage, App Check, Remote Config, Vertex AIADC-first — Application Default Credentials on Cloud Run / GKE / Cloud Functions; falls back to explicit cert() from AUTH_FIREBASE_* env vars for local dev / CI
    lib/database/adapters/FirebaseAdapter.tsFirestore IDatabaseService implementation — full CRUD, query, batch, transaction, plus 19 Enterprise methodsADC-first — cert() fallback when credentials present in backend config (matches firebase-admin.server.ts)
    lib/services/firebase-service-manager.tsReact 19 cache()-deduplicated SSOT read helpers + atomic write primitives for credit_balance, subscription_ledger, payment_transactions, wallet_transactions, desk_orders, ordersDelegates to getAdminDb()

    Related documentation

    Related documentation

    Vercel Deployment

    Same-workflow: vercel.json is the ProcessConductor schedule catalog — copy crons[] into Cloud Scheduler.

    Backend modes and databases

    Prerequisite: firebase-full vs k8s-postgres-fcm vs supabase-fcm.

    Push Notifications with FCM (Ring-Powered)

    Next-step: FCM + RFC dual-stack register/send, iOS Home Screen, empty RFC-on-Chrome no-op.

    Environment Configuration

    Depends-on: AUTH_FIREBASE_*, NEXT_PUBLIC_FIREBASE_*, and CRON_SECRET.

    Firebase Integration

    Use the Founder / Developer tabs in the docs sidebar to filter this page for your role. audience frontmatter controls content filtering within the page. Sidebar article visibility is controlled separately by lib/docs/audience-curated-docs.ts.

    Ring Platform uses Firebase in two separate paths — a server-side Admin SDK v14 (for FCM push, Firestore, Auth, Storage, App Check, and Remote Config) and a client-side browser SDK (for FCM push token registration, Auth state, Firestore real-time, and AI Logic). The two paths use different credential strategies and different SDK versions.

    This page covers the full Firebase 14 Enterprise stack in firebase-full ring-db mode: pipeline operations, change streams, text/geo search, FCM Admin HTTP v1 bridge, SSOT atomic writes, AI Logic, Firebase Hosting config, React 19 cache()-native helpers, and ProcessConductor scheduled jobs (Cloud Scheduler → the same /api/cron/* URLs as Vercel Cron).

    Backend modes

    Push notifications (FCM)

    Environment variables

    Authentication

    Local setup

    Why firebase-full mode?

    firebase-full is a first-class ring-db mode (elevated from prototyping on 2026-06-30). In this mode, all application data lives in Firebase Firestore, push notifications use Firebase Cloud Messaging, file uploads can use Firebase Storage, and the Firebase Hosting service can serve your Ring clone with a single firebase deploy command.

    What this means for your clone

    CapabilityWhat you get
    No PostgreSQL neededAll data lives in Firestore — no database server to manage, no connection pooling, no SSL certs
    Unlimited scaleFirebase manages read, write, and concurrency limits — your clone auto-scales
    Push notificationsFCM web push with React hooks, service worker, and Server Action token upsert (see Push notifications FCM)
    AI-powered featuresFirebase AI Logic for Gemini 2.5/3.x text generation, chat, and embedding — directly from the SDK

    Server-side files

    FilePurposeCredential strategy
    lib/firebase-admin.server.tsFirebase Admin SDK v14 singleton — FCM, Auth, Firestore, Storage, App Check, Remote Config, Vertex AIADC-first — Application Default Credentials on Cloud Run / GKE / Cloud Functions; falls back to explicit cert() from AUTH_FIREBASE_* env vars for local dev / CI
    lib/database/adapters/FirebaseAdapter.tsFirestore IDatabaseService implementation — full CRUD, query, batch, transaction, plus 19 Enterprise methodsADC-first — cert() fallback when credentials present in backend config (matches firebase-admin.server.ts)
    lib/services/firebase-service-manager.tsReact 19 cache()-deduplicated SSOT read helpers + atomic write primitives for credit_balance, subscription_ledger, payment_transactions, wallet_transactions, desk_orders, ordersDelegates to getAdminDb()

    Related documentation

    Related documentation

    Vercel Deployment

    Same-workflow: vercel.json is the ProcessConductor schedule catalog — copy crons[] into Cloud Scheduler.

    Backend modes and databases

    Prerequisite: firebase-full vs k8s-postgres-fcm vs supabase-fcm.

    Push Notifications with FCM (Ring-Powered)

    Next-step: FCM + RFC dual-stack register/send, iOS Home Screen, empty RFC-on-Chrome no-op.

    Environment Configuration

    Depends-on: AUTH_FIREBASE_*, NEXT_PUBLIC_FIREBASE_*, and CRON_SECRET.

    Firebase Integration

    Use the Founder / Developer tabs in the docs sidebar to filter this page for your role. audience frontmatter controls content filtering within the page. Sidebar article visibility is controlled separately by lib/docs/audience-curated-docs.ts.

    Ring Platform uses Firebase in two separate paths — a server-side Admin SDK v14 (for FCM push, Firestore, Auth, Storage, App Check, and Remote Config) and a client-side browser SDK (for FCM push token registration, Auth state, Firestore real-time, and AI Logic). The two paths use different credential strategies and different SDK versions.

    This page covers the full Firebase 14 Enterprise stack in firebase-full ring-db mode: pipeline operations, change streams, text/geo search, FCM Admin HTTP v1 bridge, SSOT atomic writes, AI Logic, Firebase Hosting config, React 19 cache()-native helpers, and ProcessConductor scheduled jobs (Cloud Scheduler → the same /api/cron/* URLs as Vercel Cron).

    Backend modes

    Push notifications (FCM)

    Environment variables

    Authentication

    Local setup

    Why firebase-full mode?

    firebase-full is a first-class ring-db mode (elevated from prototyping on 2026-06-30). In this mode, all application data lives in Firebase Firestore, push notifications use Firebase Cloud Messaging, file uploads can use Firebase Storage, and the Firebase Hosting service can serve your Ring clone with a single firebase deploy command.

    What this means for your clone

    CapabilityWhat you get
    No PostgreSQL neededAll data lives in Firestore — no database server to manage, no connection pooling, no SSL certs
    Unlimited scaleFirebase manages read, write, and concurrency limits — your clone auto-scales
    Push notificationsFCM web push with React hooks, service worker, and Server Action token upsert (see Push notifications FCM)
    AI-powered featuresFirebase AI Logic for Gemini 2.5/3.x text generation, chat, and embedding — directly from the SDK

    Server-side files

    FilePurposeCredential strategy
    lib/firebase-admin.server.tsFirebase Admin SDK v14 singleton — FCM, Auth, Firestore, Storage, App Check, Remote Config, Vertex AIADC-first — Application Default Credentials on Cloud Run / GKE / Cloud Functions; falls back to explicit cert() from AUTH_FIREBASE_* env vars for local dev / CI
    lib/database/adapters/FirebaseAdapter.tsFirestore IDatabaseService implementation — full CRUD, query, batch, transaction, plus 19 Enterprise methodsADC-first — cert() fallback when credentials present in backend config (matches firebase-admin.server.ts)
    lib/services/firebase-service-manager.tsReact 19 cache()-deduplicated SSOT read helpers + atomic write primitives for credit_balance, subscription_ledger, payment_transactions, wallet_transactions, desk_orders, ordersDelegates to getAdminDb()

    Related documentation

    Related documentation

    Vercel Deployment

    Same-workflow: vercel.json is the ProcessConductor schedule catalog — copy crons[] into Cloud Scheduler.

    Backend modes and databases

    Prerequisite: firebase-full vs k8s-postgres-fcm vs supabase-fcm.

    Push Notifications with FCM (Ring-Powered)

    Next-step: FCM + RFC dual-stack register/send, iOS Home Screen, empty RFC-on-Chrome no-op.

    Environment Configuration

    Depends-on: AUTH_FIREBASE_*, NEXT_PUBLIC_FIREBASE_*, and CRON_SECRET.

    Single deploy targetfirebase deploy --only hosting deploys your Ring clone to Firebase Hosting with TLS, CDN, and rewrite rules baked in
    No vendor lock for authAuth.js v5 handles all authentication — Firebase Auth is not used
    Local emulatorNEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 auto-connects to the Firebase Local Emulator Suite for offline development

    Architecture overview

    firebase-full ring-db mode architecture

    What Firebase features are NOT used

    • Firebase Authentication — Auth.js v5 handles all auth (Google, Apple, email, crypto wallet)
    • Firebase Realtime Database — Tunnel (lib/tunnel) is the canonical real-time transport; RTDB exports exist but are unused
    • FirebaseUI / client auth SDK — there is no lib/firebase.ts; lib/firebase-client.ts is the only client entry point

    Cost profile

    • Firestore — pay-per-read/write/delete. React 19 cache() reduces per-request read operations by 3–7×
    • FCM — free at any scale
    • Firebase App Check — included in Spark/Blaze plans
    • Firebase Hosting — free tier includes 10 GB storage, 100 GB/month transfer
    • AI Logic (Gemini) — usage-based via Vertex AI pricing; optional to enable
    • No PostgreSQL costs — no database server to provision or maintain

    Scheduled jobs — firebase.json is not a cron file

    Ring’s background health (email CRM poll, reservation cleanup, subscription expiry, settlements, and more) runs through ProcessConductor — currently 19 pipelines in lib/processes/registry.ts. Each pipeline exposes GET/POST /api/cron/<path> gated by Authorization: Bearer $CRON_SECRET.

    Critical for Firebase-hosted clones: firebase.json holds Firestore rules/indexes (and Hosting rewrites when generated). It is not a scheduler catalog. There is no Firebase-native cron entries file in the Ring tree.

    What you needWhere it lives
    Human-readable schedule catalog (UTC)vercel.json → crons[] — copy every path/schedule
    HTTP contracts (same on all backends)/api/cron/* + CRON_SECRET
    Run history labelsAdmin → Background Processes (locales/*/modules/admin.json → processes.pipelines.*)
    Trigger on Firebase Hosting / Cloud RunGoogle Cloud Scheduler (or any external cron) hitting those URLs

    Deploying Hosting alone does not schedule ProcessConductor jobs. Wire Cloud Scheduler to the same cron URLs using the Vercel schedule catalog, and set CRON_SECRET in the app env.

    Founder checklist

    1. Set a long random CRON_SECRET in the Firebase-hosted app environment.
    2. Open vercel.json in the Ring root — treat crons[] as the schedule SSOT (must list every ProcessConductor cronPath).
    3. Create one Cloud Scheduler job per entry (UTC schedule → GET or POST your public /api/cron/... URL with Authorization: Bearer <CRON_SECRET>).
    4. Confirm runs appear under Admin → Background Processes.
    5. Contrast: k3s uses per-job YAML under k8s/ (e.g. cronjob-email-processor.yaml) curling an in-cluster URL — not a single inventory file.
    lib/firebase/build-mock.server.ts
    Mock services during Next.js SSG build + k8s-postgres-fcm fallback — prevents unnecessary Firebase connections
    Returns mocks without connecting

    Client-side files

    FilePurpose
    lib/firebase-client.tsBrowser Firebase app init with 9 React 19 cache()-wrapped lazy getters — getFirebaseClientApp, getFirebaseFirestoreClient, getFirebaseAuthClient, getFirebaseStorageClient, getFirebaseAIClient
    hooks/use-fcm.ts'use client' hook wrapping getMessaging + getToken + onMessage with Server Action upsert
    components/providers/fcm-provider.tsxReact context provider for FCM state across the component tree
    public/firebase-messaging-sw.jsService worker — Firebase compat SDK v12.9.0 loaded from CDN; handles background push events

    Planned: hooks/use-firebase.ts — use-firebase hook family (useFirebaseApp, useFirestore, useFirebaseAuth, useFirestoreDoc, useFirestoreCollection, useFirestoreCache) following the same pattern as use-fcm.ts.

    Firebase Admin SDK (firebase-admin.server.ts)

    The Admin SDK uses ADC-first initialization with explicit cert() fallback. This means it works out of the box on Cloud Run, Cloud Functions, and GKE without setting AUTH_FIREBASE_CLIENT_EMAIL or AUTH_FIREBASE_PRIVATE_KEY — only AUTH_FIREBASE_PROJECT_ID is needed.

    The singleton survives Next.js dev HMR via globalThis.__RING_FIREBASE_ADMIN_APP__:

    Exports

    ExportReturnsActive inBuild-mock fallback
    getAdminApp()AppAlwaysYes
    getAdminDb()Firestorefirebase-full onlymock in other modes
    getAdminAuth()Authfirebase-full onlymock in other modes
    getAdminMessaging()MessagingAll modes (FCM)Yes — via MockMessaging
    getAdminStorage()Storagefirebase-full (planned)Yes — via MockStorage
    getAdminAppCheck()AppCheckfirebase-fullYes — via MockAppCheck
    getAdminRemoteConfig()RemoteConfigfirebase-fullYes — via MockRemoteConfig
    getAdminRtdb()Databasefirebase-full (legacy)Yes — via MockDatabase

    Utility exports: initializeAdminApp(), isAdminAppInitialized(), getAdminAppMetrics(), configureAdminDb(settings), _resetAdminAppForTests()

    In k8s-postgres-fcm and supabase-fcm modes, getAdminDb() / getAdminAuth() return mock instances from build-mock.server.ts. The mock satisfies TypeScript types but never connects to Firebase. This prevents Firebase initialization when only PostgreSQL is active.

    Environment variable cleaning (important):

    The SDK aggressively cleans env var inputs — strips surrounding quotes, converts \\n to real newlines, trims whitespace. The env.local.template convention of double-quoting AUTH_FIREBASE_PRIVATE_KEY is intentional:

    Client-side Firebase (lib/firebase-client.ts)

    Provides 9 React 19 cache()-wrapped lazy getters that are SSR-safe and window-guarded:

    GetterReturnsLazy import
    getFirebaseClientApp()FirebaseAppfirebase/app
    getFirebaseFirestoreClient()Firestorefirebase/firestore
    getFirebaseAuthClient()Authfirebase/auth
    getFirebaseStorageClient()Storage (async)firebase/storage
    getFirebaseAIClient()AI (async)firebase/ai

    Emulator support: Set NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 in .env.local to auto-connect Firestore / Auth / Storage to the Firebase Local Emulator Suite (default ports 8080, 9099, 9199). Or call connectFirebaseClientEmulator({ firestorePort?, authPort?, storagePort? }) for finer control.

    Backward-compat: The module still exports { app, db, auth } as direct named exports for hooks/use-fcm.ts and other legacy code. New code should prefer the getFirebase*Client() getters for React 19 cache() deduplication and SSR safety.

    Utility exports: validateFirebaseConfig() — returns true when all required NEXT_PUBLIC_FIREBASE_* vars are present and non-placeholder; isFcmConfigured() — returns true when config AND VAPID key are valid; isFirebaseClientReady() — SSR-safe runtime check.

    FirebaseAdapter (FirebaseAdapter.ts)

    The FirebaseAdapter implements IDatabaseService and is used only when DB_BACKEND_MODE=firebase-full. It uses Application Default Credentials (ADC) by default, falling back to explicit cert() when credentials are present in the backend config:

    IDatabaseService contract

    Firebase 14 Enterprise features (new)

    The adapter now exposes 19 new methods across 6 domains:

    1. Native accessors — raw Admin SDK instances

    2. Pipeline operations — subquery joins, bulk update/delete via Firebase 14 Enterprise:

    3. Change streams — ordered insert/update/delete events (cache invalidation, audit logs):

    4. Text search — full-text search via Cloud Firestore Enterprise text indexes:

    5. Geospatial search — radius search via Enterprise geo indexes:

    FCM Admin bridge (new)

    Server-side push notifications via HTTP v1 API. Lazy-initializes getAdminMessaging():

    All sendFcm* methods use sendEach() (not legacy sendMulticast()) and include automatic reactive cleanup of invalid tokens (messaging/registration-token-not-registered and messaging/invalid-argument errors mark tokens as invalid in the fcm_tokens collection).

    SSOT atomic writes bridge (new)

    Delegates to lib/services/firebase-service-manager.ts atomic helpers — single Firestore transaction, ACID, prevents read-modify-write race:

    Connection diagnostics

    Useful for /api/admin/firebase-health endpoints and CI smoke tests.

    firebase-service-manager (firebase-service-manager.ts)

    Provides 30+ cached Firebase operations using React 19 cache() for per-request deduplication. SSOT-aligned to the canonical collection shapes:

    Domain-specific SSOT helpers

    HelperSSOT collectionPurpose
    getCachedUserCreditBalanceusers/{userId}.credit_balanceCached fiat USD credit balance
    getCachedCreditTransactionsusers/{userId}.credit_transactions[]Credit transaction history
    getCachedLatestSubscriptionsubscription_ledgerLatest subscription (any status)
    getCachedActiveSubscriptionsubscription_ledgerActive subscription (ACTIVE + active)
    getCachedSubscriptionsDuesubscription_ledgerDue-for-payment batch (cron)
    getCachedPaymentTransactionpayment_transactionsBy order_reference
    getCachedWalletTransactionswallet_transactionsPer-user activity feed
    getCachedDeskOrderdesk_ordersCredit ↔ native-token conversion
    getCachedUserOrdersordersUser order history

    Legacy aliases preserved

    Build-time optimization

    All cached functions are only called when getAdminDb() returns real Firestore. During build (SSG), the mock Firestore methods (now including MockMessaging, MockStorage, MockAppCheck, MockRemoteConfig) return safe empty results. The cache() layer deduplicates within each request, reducing redundant Firestore reads by 3–7×.

    AI Logic bridge (new)

    Server-side Gemini 2.5/3.x text generation and chat via Firebase AI Logic. Requires optional dependency firebase-admin/vertexai + @google-cloud/vertexai. Without them, helpers return a clear not-supported error (no crash).

    For the client-side equivalent in the browser, use getFirebaseAIClient() from lib/firebase-client.ts:

    Firebase Hosting bridge (new)

    firebase-full mode supports Firebase Hosting as a production deployment target alongside k3s, Vercel, and Docker. Module-level helpers generate a canonical firebase.json for hosting rewrites/headers only — they do not register schedulers or ProcessConductor cron entries:

    The default rewrites map the canonical Ring routes:

    • /api/** → Cloud Function / App Hosting backend
    • /auth/** → Auth.js handlers
    • /trpc/** → tRPC (when present)
    • /tunnel/** → Tunnel WebSocket/SFE handler
    • /_actions/** → React 19 Server Actions
    • firebase-messaging-sw.js → Service Worker
    • ** → SPA fallback (/index.html)

    Default headers include Cache-Control: immutable for .js/.css, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, and Permissions-Policy.

    generateFirebaseJson / getRingHostingRewrites in FirebaseAdapter.ts = Hosting rewrites/headers only. Schedule ProcessConductor jobs with Cloud Scheduler against /api/cron/* — see below.

    ProcessConductor cron (Cloud Scheduler)

    Same HTTP contracts as Vercel and k3s — different trigger.

    LayerPath / artifact
    Registry SSOTlib/processes/registry.ts — PIPELINE_IDS + PIPELINE_REGISTRY (19 pipelines)
    Routesapp/api/cron/<name>/route.ts — GET/POST, fail-closed on Authorization: Bearer $CRON_SECRET
    Schedule catalogvercel.json → crons[] (UTC) — must list every cronPath
    LedgerProcessConductor.recordRun
    Admin labelslocales/*/modules/admin.json → processes.pipelines.*
    Human ops docAI-RING/scripts/PIPELINES.md § ProcessConductor cron pipelines

    Wire Google Cloud Scheduler

    1. 1

      Set CRON_SECRET in the Firebase-hosted / Cloud Run app environment (same secret you will send from Scheduler).

    2. 2

      Copy every { path, schedule } from root vercel.json crons[]. Full catalog (UTC) is also documented on Vercel deployment.

    3. 3

      For each entry, create a Cloud Scheduler job:

      • Frequency: the schedule string (UTC)
      • Target: https://<your-ring-host><path> (e.g. /api/cron/email-processor)
      • HTTP method: GET or POST (both accepted by Ring cron routes)
      • Header: Authorization: Bearer <CRON_SECRET>
    4. 4

      Smoke-test one job:

      bash
      
      curl -sS -H "Authorization: Bearer $CRON_SECRET" \
        "https://<your-ring-host>/api/cron/cleanup-usernames"

      Expect 200 when authorized; 401 when the secret is missing/wrong (fail-closed).

    5. 5

      Confirm Admin → Background Processes shows recorded runs. When adding a pipeline: update registry.ts + route + locale labels + append vercel.json (schedule catalog) + optional k8s/cronjob-*.yaml.

    Ops notes: forgejo-robot-gc supports ?dryRun=1 (classifies without deleting). forgejo-token-rotate reads FORGEJO_TOKEN_ROTATE_MAX_AGE_DAYS / FORGEJO_TOKEN_ROTATE_LIMIT. k3s contrast: per-job YAML under k8s/ curling an in-cluster URL — not a single inventory file.

    Build-time mocking (build-mock.server.ts)

    During Next.js static generation (NEXT_PHASE=phase-production-build), Firebase Admin calls are intercepted by mock services. Now includes 7 mock classes (up from 3):

    Mock classMethodsAdmin SDK type
    MockFirestorecollection, doc, get, set, update, delete, batch, runTransactionFirestore
    MockAuthgetUser, createUser, updateUser, deleteUser, listUsers, verifyIdTokenAuth
    MockDatabaseref, push, set, update, remove, once, on, orderByChild, ...Database
    MockMessagingsend, sendEach, sendAll, sendToTopic, subscribeToTopic, unsubscribeFromTopicMessaging
    MockStoragebucket, file, upload, getFiles, delete, getMetadata + defaultBucketStorage
    MockAppCheckcreateToken, verifyTokenAppCheck
    MockRemoteConfiggetTemplate, publishTemplate, rollback, listVersionsRemoteConfig

    This prevents ~22 redundant Firebase Admin initializations during SSG and reduces build time by approximately 31%.

    Environment variables

    Admin SDK (server-side)

    Client SDK (browser-side)

    isFcmConfigured() / validateFcmVapidKey() / getFcmVapidKey() in lib/firebase-client.ts require a non-placeholder NEXT_PUBLIC_FIREBASE_VAPID_KEY. Mismatched certificates fail at Firebase getToken — Ring does not classify key prefixes. Deep push map: Push notifications (FCM).

    Key differences between the two Firebase paths

    AspectAdmin SDK (firebase-admin.server.ts)Adapter (FirebaseAdapter.ts)
    CredentialsADC-first — cert() fallback when AUTH_FIREBASE_* presentADC-first — matches Admin SDK pattern
    Env varsOnly AUTH_FIREBASE_PROJECT_ID for ADC; all 3 for cert()Only AUTH_FIREBASE_PROJECT_ID for ADC
    Active inAll DB_BACKEND_MODE values (FCM always available)Only firebase-full
    Mock in postgres-primaryYes — all services returned as mocksNo — adapter not registered
    Import pathfirebase-admin/app, firebase-admin/firestore, firebase-admin/messaging, etc.Same (delegates via dynamic import)
    Versionv14 (named subpath imports)v14
    React 19 nativeglobalThis HMR-safe singleton + per-service observabilitycache()-wrapped lazy getters + async methods for use() + Suspense
    Enterprise featuresPipeline, change streams, text/geo, AI Logic (via Vertex AI)All of the above as adapter methods + module-level exports

    Authentication Architecture

    See-also: Auth.js v5 — Firebase Auth is not used.

    k8s-postgres-fcm Mode

    See-also: PostgreSQL-primary mode with FCM-only Firebase.

    Admin console

    Next-step: Admin → Background Processes labels for pipeline runs.

    Email AI-CRM

    Same-workflow: email-processor / email-analytics cron pipelines.

    typescript
    
    import { cert, initializeApp, type App } from 'firebase-admin/app'
    
    // globalThis-backed singleton prevents "already exists" errors on HMR
    const globalForAdmin = globalThis as typeof globalThis & {
      __RING_FIREBASE_ADMIN_APP__?: App
    }
    
    export function getAdminApp(): App {
      if (globalForAdmin.__RING_FIREBASE_ADMIN_APP__) {
        return globalForAdmin.__RING_FIREBASE_ADMIN_APP__
      }
      if (getApps().length > 0) {
        const app = getApps()[0]
        globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
        return app
      }
    
      // Check if we have explicit credentials for cert() fallback
      const hasExplicitCreds =
        !!process.env.AUTH_FIREBASE_PROJECT_ID &&
        !!process.env.AUTH_FIREBASE_CLIENT_EMAIL &&
        !!process.env.AUTH_FIREBASE_PRIVATE_KEY
    
      const appOptions = hasExplicitCreds
        ? { credential: cert({ /* cleaned env vars */ }) }
        : {} // ADC auto-detected from environment
    
      const app = initializeApp(appOptions)
      globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
      return app
    }
    bash
    
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----\n"
    typescript
    
    import { getFirebaseClientApp, getFirebaseFirestoreClient } from '@/lib/firebase-client'
    
    // SSR-safe: returns undefined during server render
    const app = getFirebaseClientApp()
    const db = getFirebaseFirestoreClient()
    typescript
    
    import { isEmulatorEnabled, connectFirebaseClientEmulator } from '@/lib/firebase-client'
    
    if (isEmulatorEnabled()) {
      await connectFirebaseClientEmulator()
    }
    typescript
    
    // Production (Cloud Run, GKE, Cloud Functions):
    //   ADC auto-detects from the environment — no manual cert() needed.
    //   Only AUTH_FIREBASE_PROJECT_ID required.
    
    // Local dev / CI:
    //   Falls back to explicit cert() when credentials.clientEmail and
    //   credentials.privateKey are present in the backend config.
    typescript
    
    // Single document
    await db.findById('users', userId)
    // => { success, data: T | null, error? }
    
    // Query with filters, ordering, pagination
    await db.query({
      collection: 'entities',
      filters: [{ field: 'status', operator: 'eq', value: 'active' }],
      orderBy: [{ field: 'createdAt', direction: 'desc' }],
      pagination: { limit: 20, offset: 0 },
    })
    
    // Mutations
    await db.create('users', data)
    await db.update('users', id, { name: 'New' })
    await db.delete('users', id)
    
    // Transactions
    await db.transaction(async (txn) => { ... })
    typescript
    
    const native = await adapter.getNativeFirestore()
    const auth = await adapter.getNativeAuth()
    const messaging = await adapter.getNativeMessaging()
    const storage = await adapter.getNativeStorage()
    const appCheck = await adapter.getNativeAppCheck()
    typescript
    
    const result = await adapter.runPipeline('orders', [
      { where: { field: 'status', op: '==', value: 'paid' } },
      { aggregate: { sum: { field: 'amount' }, as: 'total' } },
      { limit: 10 },
    ])
    typescript
    
    const { data } = await adapter.onCollectionChange('subscription_ledger', (event) => {
      if (event.type === 'modified') {
        publishToChannel(event.doc.id, 'subscription:update', event.doc)
      }
    })
    typescript
    
    const { data } = await adapter.textSearch('entities', 'organic coffee', {
      field: 'description',
      limit: 20,
    })
    typescript
    
    const { data } = await adapter.geoSearch('entities',
      { latitude: 49.44, longitude: 32.06 },
      50, // radius in km
      { field: 'location', limit: 20 },
    )
    typescript
    
    // Single FCM message
    await adapter.sendFcmMessage({
      token: 'fcm-registration-token',
      notification: { title: 'Hello', body: 'World' },
      webpush: { fcmOptions: { link: 'https://ring-platform.org/notifications' } },
    })
    
    // Multi-token fan-out to a specific user (auto-cleans dead tokens)
    await adapter.sendFcmToUser(userId, { title: 'New message', body: 'Text' },
      { route: '/messages/123' },
    )
    
    // Topic broadcast
    await adapter.sendFcmToTopic('global-announcements', { title: 'Maintenance', body: 'Tonight 2-4 AM' })
    
    // Token validation
    const { data } = await adapter.validateFcmToken(token)
    if (!data?.valid) { /* remove from DB */ }
    
    // Reactive cleanup
    await adapter.cleanupInvalidFcmTokens(['dead-token-1', 'dead-token-2'])
    typescript
    
    // Atomic credit balance adjust (fiat USD)
    await adapter.creditBalanceAdjust(userId, -10, { id: 'ct_xxx', description: 'Membership fee' })
    
    // Atomic subscription status transition (ledger + user doc)
    await adapter.subscriptionStatusUpdate(subId, 'cancelled',
      { membership: { tier: 'SUBSCRIBER' } },
      { cancelled_at: Date.now(), auto_renew: false },
    )
    
    // Atomic payment transaction status append (status_history prevention)
    await adapter.paymentStatusAppend(orderRef, 'paid', { processor_payload: { ... } })
    typescript
    
    const diagnostics = await adapter.diagnoseConnection()
    // => { backendMode, firebaseConfigured, services: { firestore: true, ... }, errors: [] }
    typescript
    
    getUserCreditTransactions: getCachedCreditTransactions, // legacy → SSOT
    getActiveSubscriptions: getCachedSubscriptionsDue,       // legacy → SSOT
    getUserOrders: getCachedUserOrders,                      // legacy → SSOT
    typescript
    
    import { aiGenerateText, aiChatCompletion } from '@/lib/database/adapters/FirebaseAdapter'
    
    // Text generation
    const result = await aiGenerateText({
      prompt: 'What is the Ring Platform?',
      model: 'gemini-2.5-flash',
      temperature: 0.7,
      maxOutputTokens: 1024,
    })
    
    if (result.success) {
      console.log(result.data!.text, result.data!.usage)
    }
    
    // Multi-turn chat
    const chatResult = await aiChatCompletion({
      model: 'gemini-2.5-flash',
      messages: [
        { role: 'system', content: 'You are a Ring Platform expert.' },
        { role: 'user', content: 'How do I set up firebase-full?' },
      ],
    })
    typescript
    
    import { getFirebaseAIClient } from '@/lib/firebase-client'
    
    const ai = await getFirebaseAIClient()
    if (ai) {
      const result = await ai.getGenerativeModel({ model: 'gemini-2.5-flash' }).generateContent('...')
    }
    typescript
    
    import { generateFirebaseJson, getRingHostingRewrites } from '@/lib/database/adapters/FirebaseAdapter'
    import { writeFile } from 'fs/promises'
    
    const config = generateFirebaseJson({
      public: 'out',
      rewrites: getRingHostingRewrites(), // /api/** → /api, /auth/** → /auth, ** → /index.html
    })
    
    await writeFile('firebase.json', JSON.stringify(config, null, 2))
    // Then: firebase deploy --only hosting
    bash
    
    # For ADC (Cloud Run / GKE / Cloud Functions — recommended):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    
    # For explicit cert() fallback (local dev / CI — all three required):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    AUTH_FIREBASE_CLIENT_EMAIL=your_firebase_client_email
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
    
    # Optional:
    FIREBASE_DATABASE_URL=https://your-project-default-rtdb.firebaseio.com
    FIREBASE_STORAGE_BUCKET=your-project.appspot.com
    FIREBASE_PRIVATE_KEY_ID=your_firebase_private_key_id
    FIREBASE_CLIENT_ID=your_firebase_client_id
    FIREBASE_FIRESTORE_DEBUG=true
    bash
    
    NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
    NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain
    NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_firebase_project_id
    NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket
    NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
    NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
    NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-YCZKPV315E
    NEXT_PUBLIC_FIREBASE_VAPID_KEY=your_vapid_key
    # ↑ Cloud Messaging → Web Push certificates (same project). Rebuild after rotate.
    NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1  # Enables Firebase Local Emulator Suite auto-connect
    Single deploy targetfirebase deploy --only hosting deploys your Ring clone to Firebase Hosting with TLS, CDN, and rewrite rules baked in
    No vendor lock for authAuth.js v5 handles all authentication — Firebase Auth is not used
    Local emulatorNEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 auto-connects to the Firebase Local Emulator Suite for offline development

    Architecture overview

    firebase-full ring-db mode architecture

    What Firebase features are NOT used

    • Firebase Authentication — Auth.js v5 handles all auth (Google, Apple, email, crypto wallet)
    • Firebase Realtime Database — Tunnel (lib/tunnel) is the canonical real-time transport; RTDB exports exist but are unused
    • FirebaseUI / client auth SDK — there is no lib/firebase.ts; lib/firebase-client.ts is the only client entry point

    Cost profile

    • Firestore — pay-per-read/write/delete. React 19 cache() reduces per-request read operations by 3–7×
    • FCM — free at any scale
    • Firebase App Check — included in Spark/Blaze plans
    • Firebase Hosting — free tier includes 10 GB storage, 100 GB/month transfer
    • AI Logic (Gemini) — usage-based via Vertex AI pricing; optional to enable
    • No PostgreSQL costs — no database server to provision or maintain

    Scheduled jobs — firebase.json is not a cron file

    Ring’s background health (email CRM poll, reservation cleanup, subscription expiry, settlements, and more) runs through ProcessConductor — currently 19 pipelines in lib/processes/registry.ts. Each pipeline exposes GET/POST /api/cron/<path> gated by Authorization: Bearer $CRON_SECRET.

    Critical for Firebase-hosted clones: firebase.json holds Firestore rules/indexes (and Hosting rewrites when generated). It is not a scheduler catalog. There is no Firebase-native cron entries file in the Ring tree.

    What you needWhere it lives
    Human-readable schedule catalog (UTC)vercel.json → crons[] — copy every path/schedule
    HTTP contracts (same on all backends)/api/cron/* + CRON_SECRET
    Run history labelsAdmin → Background Processes (locales/*/modules/admin.json → processes.pipelines.*)
    Trigger on Firebase Hosting / Cloud RunGoogle Cloud Scheduler (or any external cron) hitting those URLs

    Deploying Hosting alone does not schedule ProcessConductor jobs. Wire Cloud Scheduler to the same cron URLs using the Vercel schedule catalog, and set CRON_SECRET in the app env.

    Founder checklist

    1. Set a long random CRON_SECRET in the Firebase-hosted app environment.
    2. Open vercel.json in the Ring root — treat crons[] as the schedule SSOT (must list every ProcessConductor cronPath).
    3. Create one Cloud Scheduler job per entry (UTC schedule → GET or POST your public /api/cron/... URL with Authorization: Bearer <CRON_SECRET>).
    4. Confirm runs appear under Admin → Background Processes.
    5. Contrast: k3s uses per-job YAML under k8s/ (e.g. cronjob-email-processor.yaml) curling an in-cluster URL — not a single inventory file.
    lib/firebase/build-mock.server.ts
    Mock services during Next.js SSG build + k8s-postgres-fcm fallback — prevents unnecessary Firebase connections
    Returns mocks without connecting

    Client-side files

    FilePurpose
    lib/firebase-client.tsBrowser Firebase app init with 9 React 19 cache()-wrapped lazy getters — getFirebaseClientApp, getFirebaseFirestoreClient, getFirebaseAuthClient, getFirebaseStorageClient, getFirebaseAIClient
    hooks/use-fcm.ts'use client' hook wrapping getMessaging + getToken + onMessage with Server Action upsert
    components/providers/fcm-provider.tsxReact context provider for FCM state across the component tree
    public/firebase-messaging-sw.jsService worker — Firebase compat SDK v12.9.0 loaded from CDN; handles background push events

    Planned: hooks/use-firebase.ts — use-firebase hook family (useFirebaseApp, useFirestore, useFirebaseAuth, useFirestoreDoc, useFirestoreCollection, useFirestoreCache) following the same pattern as use-fcm.ts.

    Firebase Admin SDK (firebase-admin.server.ts)

    The Admin SDK uses ADC-first initialization with explicit cert() fallback. This means it works out of the box on Cloud Run, Cloud Functions, and GKE without setting AUTH_FIREBASE_CLIENT_EMAIL or AUTH_FIREBASE_PRIVATE_KEY — only AUTH_FIREBASE_PROJECT_ID is needed.

    The singleton survives Next.js dev HMR via globalThis.__RING_FIREBASE_ADMIN_APP__:

    Exports

    ExportReturnsActive inBuild-mock fallback
    getAdminApp()AppAlwaysYes
    getAdminDb()Firestorefirebase-full onlymock in other modes
    getAdminAuth()Authfirebase-full onlymock in other modes
    getAdminMessaging()MessagingAll modes (FCM)Yes — via MockMessaging
    getAdminStorage()Storagefirebase-full (planned)Yes — via MockStorage
    getAdminAppCheck()AppCheckfirebase-fullYes — via MockAppCheck
    getAdminRemoteConfig()RemoteConfigfirebase-fullYes — via MockRemoteConfig
    getAdminRtdb()Databasefirebase-full (legacy)Yes — via MockDatabase

    Utility exports: initializeAdminApp(), isAdminAppInitialized(), getAdminAppMetrics(), configureAdminDb(settings), _resetAdminAppForTests()

    In k8s-postgres-fcm and supabase-fcm modes, getAdminDb() / getAdminAuth() return mock instances from build-mock.server.ts. The mock satisfies TypeScript types but never connects to Firebase. This prevents Firebase initialization when only PostgreSQL is active.

    Environment variable cleaning (important):

    The SDK aggressively cleans env var inputs — strips surrounding quotes, converts \\n to real newlines, trims whitespace. The env.local.template convention of double-quoting AUTH_FIREBASE_PRIVATE_KEY is intentional:

    Client-side Firebase (lib/firebase-client.ts)

    Provides 9 React 19 cache()-wrapped lazy getters that are SSR-safe and window-guarded:

    GetterReturnsLazy import
    getFirebaseClientApp()FirebaseAppfirebase/app
    getFirebaseFirestoreClient()Firestorefirebase/firestore
    getFirebaseAuthClient()Authfirebase/auth
    getFirebaseStorageClient()Storage (async)firebase/storage
    getFirebaseAIClient()AI (async)firebase/ai

    Emulator support: Set NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 in .env.local to auto-connect Firestore / Auth / Storage to the Firebase Local Emulator Suite (default ports 8080, 9099, 9199). Or call connectFirebaseClientEmulator({ firestorePort?, authPort?, storagePort? }) for finer control.

    Backward-compat: The module still exports { app, db, auth } as direct named exports for hooks/use-fcm.ts and other legacy code. New code should prefer the getFirebase*Client() getters for React 19 cache() deduplication and SSR safety.

    Utility exports: validateFirebaseConfig() — returns true when all required NEXT_PUBLIC_FIREBASE_* vars are present and non-placeholder; isFcmConfigured() — returns true when config AND VAPID key are valid; isFirebaseClientReady() — SSR-safe runtime check.

    FirebaseAdapter (FirebaseAdapter.ts)

    The FirebaseAdapter implements IDatabaseService and is used only when DB_BACKEND_MODE=firebase-full. It uses Application Default Credentials (ADC) by default, falling back to explicit cert() when credentials are present in the backend config:

    IDatabaseService contract

    Firebase 14 Enterprise features (new)

    The adapter now exposes 19 new methods across 6 domains:

    1. Native accessors — raw Admin SDK instances

    2. Pipeline operations — subquery joins, bulk update/delete via Firebase 14 Enterprise:

    3. Change streams — ordered insert/update/delete events (cache invalidation, audit logs):

    4. Text search — full-text search via Cloud Firestore Enterprise text indexes:

    5. Geospatial search — radius search via Enterprise geo indexes:

    FCM Admin bridge (new)

    Server-side push notifications via HTTP v1 API. Lazy-initializes getAdminMessaging():

    All sendFcm* methods use sendEach() (not legacy sendMulticast()) and include automatic reactive cleanup of invalid tokens (messaging/registration-token-not-registered and messaging/invalid-argument errors mark tokens as invalid in the fcm_tokens collection).

    SSOT atomic writes bridge (new)

    Delegates to lib/services/firebase-service-manager.ts atomic helpers — single Firestore transaction, ACID, prevents read-modify-write race:

    Connection diagnostics

    Useful for /api/admin/firebase-health endpoints and CI smoke tests.

    firebase-service-manager (firebase-service-manager.ts)

    Provides 30+ cached Firebase operations using React 19 cache() for per-request deduplication. SSOT-aligned to the canonical collection shapes:

    Domain-specific SSOT helpers

    HelperSSOT collectionPurpose
    getCachedUserCreditBalanceusers/{userId}.credit_balanceCached fiat USD credit balance
    getCachedCreditTransactionsusers/{userId}.credit_transactions[]Credit transaction history
    getCachedLatestSubscriptionsubscription_ledgerLatest subscription (any status)
    getCachedActiveSubscriptionsubscription_ledgerActive subscription (ACTIVE + active)
    getCachedSubscriptionsDuesubscription_ledgerDue-for-payment batch (cron)
    getCachedPaymentTransactionpayment_transactionsBy order_reference
    getCachedWalletTransactionswallet_transactionsPer-user activity feed
    getCachedDeskOrderdesk_ordersCredit ↔ native-token conversion
    getCachedUserOrdersordersUser order history

    Legacy aliases preserved

    Build-time optimization

    All cached functions are only called when getAdminDb() returns real Firestore. During build (SSG), the mock Firestore methods (now including MockMessaging, MockStorage, MockAppCheck, MockRemoteConfig) return safe empty results. The cache() layer deduplicates within each request, reducing redundant Firestore reads by 3–7×.

    AI Logic bridge (new)

    Server-side Gemini 2.5/3.x text generation and chat via Firebase AI Logic. Requires optional dependency firebase-admin/vertexai + @google-cloud/vertexai. Without them, helpers return a clear not-supported error (no crash).

    For the client-side equivalent in the browser, use getFirebaseAIClient() from lib/firebase-client.ts:

    Firebase Hosting bridge (new)

    firebase-full mode supports Firebase Hosting as a production deployment target alongside k3s, Vercel, and Docker. Module-level helpers generate a canonical firebase.json for hosting rewrites/headers only — they do not register schedulers or ProcessConductor cron entries:

    The default rewrites map the canonical Ring routes:

    • /api/** → Cloud Function / App Hosting backend
    • /auth/** → Auth.js handlers
    • /trpc/** → tRPC (when present)
    • /tunnel/** → Tunnel WebSocket/SFE handler
    • /_actions/** → React 19 Server Actions
    • firebase-messaging-sw.js → Service Worker
    • ** → SPA fallback (/index.html)

    Default headers include Cache-Control: immutable for .js/.css, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, and Permissions-Policy.

    generateFirebaseJson / getRingHostingRewrites in FirebaseAdapter.ts = Hosting rewrites/headers only. Schedule ProcessConductor jobs with Cloud Scheduler against /api/cron/* — see below.

    ProcessConductor cron (Cloud Scheduler)

    Same HTTP contracts as Vercel and k3s — different trigger.

    LayerPath / artifact
    Registry SSOTlib/processes/registry.ts — PIPELINE_IDS + PIPELINE_REGISTRY (19 pipelines)
    Routesapp/api/cron/<name>/route.ts — GET/POST, fail-closed on Authorization: Bearer $CRON_SECRET
    Schedule catalogvercel.json → crons[] (UTC) — must list every cronPath
    LedgerProcessConductor.recordRun
    Admin labelslocales/*/modules/admin.json → processes.pipelines.*
    Human ops docAI-RING/scripts/PIPELINES.md § ProcessConductor cron pipelines

    Wire Google Cloud Scheduler

    1. 1

      Set CRON_SECRET in the Firebase-hosted / Cloud Run app environment (same secret you will send from Scheduler).

    2. 2

      Copy every { path, schedule } from root vercel.json crons[]. Full catalog (UTC) is also documented on Vercel deployment.

    3. 3

      For each entry, create a Cloud Scheduler job:

      • Frequency: the schedule string (UTC)
      • Target: https://<your-ring-host><path> (e.g. /api/cron/email-processor)
      • HTTP method: GET or POST (both accepted by Ring cron routes)
      • Header: Authorization: Bearer <CRON_SECRET>
    4. 4

      Smoke-test one job:

      bash
      
      curl -sS -H "Authorization: Bearer $CRON_SECRET" \
        "https://<your-ring-host>/api/cron/cleanup-usernames"

      Expect 200 when authorized; 401 when the secret is missing/wrong (fail-closed).

    5. 5

      Confirm Admin → Background Processes shows recorded runs. When adding a pipeline: update registry.ts + route + locale labels + append vercel.json (schedule catalog) + optional k8s/cronjob-*.yaml.

    Ops notes: forgejo-robot-gc supports ?dryRun=1 (classifies without deleting). forgejo-token-rotate reads FORGEJO_TOKEN_ROTATE_MAX_AGE_DAYS / FORGEJO_TOKEN_ROTATE_LIMIT. k3s contrast: per-job YAML under k8s/ curling an in-cluster URL — not a single inventory file.

    Build-time mocking (build-mock.server.ts)

    During Next.js static generation (NEXT_PHASE=phase-production-build), Firebase Admin calls are intercepted by mock services. Now includes 7 mock classes (up from 3):

    Mock classMethodsAdmin SDK type
    MockFirestorecollection, doc, get, set, update, delete, batch, runTransactionFirestore
    MockAuthgetUser, createUser, updateUser, deleteUser, listUsers, verifyIdTokenAuth
    MockDatabaseref, push, set, update, remove, once, on, orderByChild, ...Database
    MockMessagingsend, sendEach, sendAll, sendToTopic, subscribeToTopic, unsubscribeFromTopicMessaging
    MockStoragebucket, file, upload, getFiles, delete, getMetadata + defaultBucketStorage
    MockAppCheckcreateToken, verifyTokenAppCheck
    MockRemoteConfiggetTemplate, publishTemplate, rollback, listVersionsRemoteConfig

    This prevents ~22 redundant Firebase Admin initializations during SSG and reduces build time by approximately 31%.

    Environment variables

    Admin SDK (server-side)

    Client SDK (browser-side)

    isFcmConfigured() / validateFcmVapidKey() / getFcmVapidKey() in lib/firebase-client.ts require a non-placeholder NEXT_PUBLIC_FIREBASE_VAPID_KEY. Mismatched certificates fail at Firebase getToken — Ring does not classify key prefixes. Deep push map: Push notifications (FCM).

    Key differences between the two Firebase paths

    AspectAdmin SDK (firebase-admin.server.ts)Adapter (FirebaseAdapter.ts)
    CredentialsADC-first — cert() fallback when AUTH_FIREBASE_* presentADC-first — matches Admin SDK pattern
    Env varsOnly AUTH_FIREBASE_PROJECT_ID for ADC; all 3 for cert()Only AUTH_FIREBASE_PROJECT_ID for ADC
    Active inAll DB_BACKEND_MODE values (FCM always available)Only firebase-full
    Mock in postgres-primaryYes — all services returned as mocksNo — adapter not registered
    Import pathfirebase-admin/app, firebase-admin/firestore, firebase-admin/messaging, etc.Same (delegates via dynamic import)
    Versionv14 (named subpath imports)v14
    React 19 nativeglobalThis HMR-safe singleton + per-service observabilitycache()-wrapped lazy getters + async methods for use() + Suspense
    Enterprise featuresPipeline, change streams, text/geo, AI Logic (via Vertex AI)All of the above as adapter methods + module-level exports

    Authentication Architecture

    See-also: Auth.js v5 — Firebase Auth is not used.

    k8s-postgres-fcm Mode

    See-also: PostgreSQL-primary mode with FCM-only Firebase.

    Admin console

    Next-step: Admin → Background Processes labels for pipeline runs.

    Email AI-CRM

    Same-workflow: email-processor / email-analytics cron pipelines.

    typescript
    
    import { cert, initializeApp, type App } from 'firebase-admin/app'
    
    // globalThis-backed singleton prevents "already exists" errors on HMR
    const globalForAdmin = globalThis as typeof globalThis & {
      __RING_FIREBASE_ADMIN_APP__?: App
    }
    
    export function getAdminApp(): App {
      if (globalForAdmin.__RING_FIREBASE_ADMIN_APP__) {
        return globalForAdmin.__RING_FIREBASE_ADMIN_APP__
      }
      if (getApps().length > 0) {
        const app = getApps()[0]
        globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
        return app
      }
    
      // Check if we have explicit credentials for cert() fallback
      const hasExplicitCreds =
        !!process.env.AUTH_FIREBASE_PROJECT_ID &&
        !!process.env.AUTH_FIREBASE_CLIENT_EMAIL &&
        !!process.env.AUTH_FIREBASE_PRIVATE_KEY
    
      const appOptions = hasExplicitCreds
        ? { credential: cert({ /* cleaned env vars */ }) }
        : {} // ADC auto-detected from environment
    
      const app = initializeApp(appOptions)
      globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
      return app
    }
    bash
    
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----\n"
    typescript
    
    import { getFirebaseClientApp, getFirebaseFirestoreClient } from '@/lib/firebase-client'
    
    // SSR-safe: returns undefined during server render
    const app = getFirebaseClientApp()
    const db = getFirebaseFirestoreClient()
    typescript
    
    import { isEmulatorEnabled, connectFirebaseClientEmulator } from '@/lib/firebase-client'
    
    if (isEmulatorEnabled()) {
      await connectFirebaseClientEmulator()
    }
    typescript
    
    // Production (Cloud Run, GKE, Cloud Functions):
    //   ADC auto-detects from the environment — no manual cert() needed.
    //   Only AUTH_FIREBASE_PROJECT_ID required.
    
    // Local dev / CI:
    //   Falls back to explicit cert() when credentials.clientEmail and
    //   credentials.privateKey are present in the backend config.
    typescript
    
    // Single document
    await db.findById('users', userId)
    // => { success, data: T | null, error? }
    
    // Query with filters, ordering, pagination
    await db.query({
      collection: 'entities',
      filters: [{ field: 'status', operator: 'eq', value: 'active' }],
      orderBy: [{ field: 'createdAt', direction: 'desc' }],
      pagination: { limit: 20, offset: 0 },
    })
    
    // Mutations
    await db.create('users', data)
    await db.update('users', id, { name: 'New' })
    await db.delete('users', id)
    
    // Transactions
    await db.transaction(async (txn) => { ... })
    typescript
    
    const native = await adapter.getNativeFirestore()
    const auth = await adapter.getNativeAuth()
    const messaging = await adapter.getNativeMessaging()
    const storage = await adapter.getNativeStorage()
    const appCheck = await adapter.getNativeAppCheck()
    typescript
    
    const result = await adapter.runPipeline('orders', [
      { where: { field: 'status', op: '==', value: 'paid' } },
      { aggregate: { sum: { field: 'amount' }, as: 'total' } },
      { limit: 10 },
    ])
    typescript
    
    const { data } = await adapter.onCollectionChange('subscription_ledger', (event) => {
      if (event.type === 'modified') {
        publishToChannel(event.doc.id, 'subscription:update', event.doc)
      }
    })
    typescript
    
    const { data } = await adapter.textSearch('entities', 'organic coffee', {
      field: 'description',
      limit: 20,
    })
    typescript
    
    const { data } = await adapter.geoSearch('entities',
      { latitude: 49.44, longitude: 32.06 },
      50, // radius in km
      { field: 'location', limit: 20 },
    )
    typescript
    
    // Single FCM message
    await adapter.sendFcmMessage({
      token: 'fcm-registration-token',
      notification: { title: 'Hello', body: 'World' },
      webpush: { fcmOptions: { link: 'https://ring-platform.org/notifications' } },
    })
    
    // Multi-token fan-out to a specific user (auto-cleans dead tokens)
    await adapter.sendFcmToUser(userId, { title: 'New message', body: 'Text' },
      { route: '/messages/123' },
    )
    
    // Topic broadcast
    await adapter.sendFcmToTopic('global-announcements', { title: 'Maintenance', body: 'Tonight 2-4 AM' })
    
    // Token validation
    const { data } = await adapter.validateFcmToken(token)
    if (!data?.valid) { /* remove from DB */ }
    
    // Reactive cleanup
    await adapter.cleanupInvalidFcmTokens(['dead-token-1', 'dead-token-2'])
    typescript
    
    // Atomic credit balance adjust (fiat USD)
    await adapter.creditBalanceAdjust(userId, -10, { id: 'ct_xxx', description: 'Membership fee' })
    
    // Atomic subscription status transition (ledger + user doc)
    await adapter.subscriptionStatusUpdate(subId, 'cancelled',
      { membership: { tier: 'SUBSCRIBER' } },
      { cancelled_at: Date.now(), auto_renew: false },
    )
    
    // Atomic payment transaction status append (status_history prevention)
    await adapter.paymentStatusAppend(orderRef, 'paid', { processor_payload: { ... } })
    typescript
    
    const diagnostics = await adapter.diagnoseConnection()
    // => { backendMode, firebaseConfigured, services: { firestore: true, ... }, errors: [] }
    typescript
    
    getUserCreditTransactions: getCachedCreditTransactions, // legacy → SSOT
    getActiveSubscriptions: getCachedSubscriptionsDue,       // legacy → SSOT
    getUserOrders: getCachedUserOrders,                      // legacy → SSOT
    typescript
    
    import { aiGenerateText, aiChatCompletion } from '@/lib/database/adapters/FirebaseAdapter'
    
    // Text generation
    const result = await aiGenerateText({
      prompt: 'What is the Ring Platform?',
      model: 'gemini-2.5-flash',
      temperature: 0.7,
      maxOutputTokens: 1024,
    })
    
    if (result.success) {
      console.log(result.data!.text, result.data!.usage)
    }
    
    // Multi-turn chat
    const chatResult = await aiChatCompletion({
      model: 'gemini-2.5-flash',
      messages: [
        { role: 'system', content: 'You are a Ring Platform expert.' },
        { role: 'user', content: 'How do I set up firebase-full?' },
      ],
    })
    typescript
    
    import { getFirebaseAIClient } from '@/lib/firebase-client'
    
    const ai = await getFirebaseAIClient()
    if (ai) {
      const result = await ai.getGenerativeModel({ model: 'gemini-2.5-flash' }).generateContent('...')
    }
    typescript
    
    import { generateFirebaseJson, getRingHostingRewrites } from '@/lib/database/adapters/FirebaseAdapter'
    import { writeFile } from 'fs/promises'
    
    const config = generateFirebaseJson({
      public: 'out',
      rewrites: getRingHostingRewrites(), // /api/** → /api, /auth/** → /auth, ** → /index.html
    })
    
    await writeFile('firebase.json', JSON.stringify(config, null, 2))
    // Then: firebase deploy --only hosting
    bash
    
    # For ADC (Cloud Run / GKE / Cloud Functions — recommended):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    
    # For explicit cert() fallback (local dev / CI — all three required):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    AUTH_FIREBASE_CLIENT_EMAIL=your_firebase_client_email
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
    
    # Optional:
    FIREBASE_DATABASE_URL=https://your-project-default-rtdb.firebaseio.com
    FIREBASE_STORAGE_BUCKET=your-project.appspot.com
    FIREBASE_PRIVATE_KEY_ID=your_firebase_private_key_id
    FIREBASE_CLIENT_ID=your_firebase_client_id
    FIREBASE_FIRESTORE_DEBUG=true
    bash
    
    NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
    NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain
    NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_firebase_project_id
    NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket
    NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
    NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
    NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-YCZKPV315E
    NEXT_PUBLIC_FIREBASE_VAPID_KEY=your_vapid_key
    # ↑ Cloud Messaging → Web Push certificates (same project). Rebuild after rotate.
    NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1  # Enables Firebase Local Emulator Suite auto-connect
    Single deploy targetfirebase deploy --only hosting deploys your Ring clone to Firebase Hosting with TLS, CDN, and rewrite rules baked in
    No vendor lock for authAuth.js v5 handles all authentication — Firebase Auth is not used
    Local emulatorNEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 auto-connects to the Firebase Local Emulator Suite for offline development

    Architecture overview

    firebase-full ring-db mode architecture

    What Firebase features are NOT used

    • Firebase Authentication — Auth.js v5 handles all auth (Google, Apple, email, crypto wallet)
    • Firebase Realtime Database — Tunnel (lib/tunnel) is the canonical real-time transport; RTDB exports exist but are unused
    • FirebaseUI / client auth SDK — there is no lib/firebase.ts; lib/firebase-client.ts is the only client entry point

    Cost profile

    • Firestore — pay-per-read/write/delete. React 19 cache() reduces per-request read operations by 3–7×
    • FCM — free at any scale
    • Firebase App Check — included in Spark/Blaze plans
    • Firebase Hosting — free tier includes 10 GB storage, 100 GB/month transfer
    • AI Logic (Gemini) — usage-based via Vertex AI pricing; optional to enable
    • No PostgreSQL costs — no database server to provision or maintain

    Scheduled jobs — firebase.json is not a cron file

    Ring’s background health (email CRM poll, reservation cleanup, subscription expiry, settlements, and more) runs through ProcessConductor — currently 19 pipelines in lib/processes/registry.ts. Each pipeline exposes GET/POST /api/cron/<path> gated by Authorization: Bearer $CRON_SECRET.

    Critical for Firebase-hosted clones: firebase.json holds Firestore rules/indexes (and Hosting rewrites when generated). It is not a scheduler catalog. There is no Firebase-native cron entries file in the Ring tree.

    What you needWhere it lives
    Human-readable schedule catalog (UTC)vercel.json → crons[] — copy every path/schedule
    HTTP contracts (same on all backends)/api/cron/* + CRON_SECRET
    Run history labelsAdmin → Background Processes (locales/*/modules/admin.json → processes.pipelines.*)
    Trigger on Firebase Hosting / Cloud RunGoogle Cloud Scheduler (or any external cron) hitting those URLs

    Deploying Hosting alone does not schedule ProcessConductor jobs. Wire Cloud Scheduler to the same cron URLs using the Vercel schedule catalog, and set CRON_SECRET in the app env.

    Founder checklist

    1. Set a long random CRON_SECRET in the Firebase-hosted app environment.
    2. Open vercel.json in the Ring root — treat crons[] as the schedule SSOT (must list every ProcessConductor cronPath).
    3. Create one Cloud Scheduler job per entry (UTC schedule → GET or POST your public /api/cron/... URL with Authorization: Bearer <CRON_SECRET>).
    4. Confirm runs appear under Admin → Background Processes.
    5. Contrast: k3s uses per-job YAML under k8s/ (e.g. cronjob-email-processor.yaml) curling an in-cluster URL — not a single inventory file.
    lib/firebase/build-mock.server.ts
    Mock services during Next.js SSG build + k8s-postgres-fcm fallback — prevents unnecessary Firebase connections
    Returns mocks without connecting

    Client-side files

    FilePurpose
    lib/firebase-client.tsBrowser Firebase app init with 9 React 19 cache()-wrapped lazy getters — getFirebaseClientApp, getFirebaseFirestoreClient, getFirebaseAuthClient, getFirebaseStorageClient, getFirebaseAIClient
    hooks/use-fcm.ts'use client' hook wrapping getMessaging + getToken + onMessage with Server Action upsert
    components/providers/fcm-provider.tsxReact context provider for FCM state across the component tree
    public/firebase-messaging-sw.jsService worker — Firebase compat SDK v12.9.0 loaded from CDN; handles background push events

    Planned: hooks/use-firebase.ts — use-firebase hook family (useFirebaseApp, useFirestore, useFirebaseAuth, useFirestoreDoc, useFirestoreCollection, useFirestoreCache) following the same pattern as use-fcm.ts.

    Firebase Admin SDK (firebase-admin.server.ts)

    The Admin SDK uses ADC-first initialization with explicit cert() fallback. This means it works out of the box on Cloud Run, Cloud Functions, and GKE without setting AUTH_FIREBASE_CLIENT_EMAIL or AUTH_FIREBASE_PRIVATE_KEY — only AUTH_FIREBASE_PROJECT_ID is needed.

    The singleton survives Next.js dev HMR via globalThis.__RING_FIREBASE_ADMIN_APP__:

    Exports

    ExportReturnsActive inBuild-mock fallback
    getAdminApp()AppAlwaysYes
    getAdminDb()Firestorefirebase-full onlymock in other modes
    getAdminAuth()Authfirebase-full onlymock in other modes
    getAdminMessaging()MessagingAll modes (FCM)Yes — via MockMessaging
    getAdminStorage()Storagefirebase-full (planned)Yes — via MockStorage
    getAdminAppCheck()AppCheckfirebase-fullYes — via MockAppCheck
    getAdminRemoteConfig()RemoteConfigfirebase-fullYes — via MockRemoteConfig
    getAdminRtdb()Databasefirebase-full (legacy)Yes — via MockDatabase

    Utility exports: initializeAdminApp(), isAdminAppInitialized(), getAdminAppMetrics(), configureAdminDb(settings), _resetAdminAppForTests()

    In k8s-postgres-fcm and supabase-fcm modes, getAdminDb() / getAdminAuth() return mock instances from build-mock.server.ts. The mock satisfies TypeScript types but never connects to Firebase. This prevents Firebase initialization when only PostgreSQL is active.

    Environment variable cleaning (important):

    The SDK aggressively cleans env var inputs — strips surrounding quotes, converts \\n to real newlines, trims whitespace. The env.local.template convention of double-quoting AUTH_FIREBASE_PRIVATE_KEY is intentional:

    Client-side Firebase (lib/firebase-client.ts)

    Provides 9 React 19 cache()-wrapped lazy getters that are SSR-safe and window-guarded:

    GetterReturnsLazy import
    getFirebaseClientApp()FirebaseAppfirebase/app
    getFirebaseFirestoreClient()Firestorefirebase/firestore
    getFirebaseAuthClient()Authfirebase/auth
    getFirebaseStorageClient()Storage (async)firebase/storage
    getFirebaseAIClient()AI (async)firebase/ai

    Emulator support: Set NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1 in .env.local to auto-connect Firestore / Auth / Storage to the Firebase Local Emulator Suite (default ports 8080, 9099, 9199). Or call connectFirebaseClientEmulator({ firestorePort?, authPort?, storagePort? }) for finer control.

    Backward-compat: The module still exports { app, db, auth } as direct named exports for hooks/use-fcm.ts and other legacy code. New code should prefer the getFirebase*Client() getters for React 19 cache() deduplication and SSR safety.

    Utility exports: validateFirebaseConfig() — returns true when all required NEXT_PUBLIC_FIREBASE_* vars are present and non-placeholder; isFcmConfigured() — returns true when config AND VAPID key are valid; isFirebaseClientReady() — SSR-safe runtime check.

    FirebaseAdapter (FirebaseAdapter.ts)

    The FirebaseAdapter implements IDatabaseService and is used only when DB_BACKEND_MODE=firebase-full. It uses Application Default Credentials (ADC) by default, falling back to explicit cert() when credentials are present in the backend config:

    IDatabaseService contract

    Firebase 14 Enterprise features (new)

    The adapter now exposes 19 new methods across 6 domains:

    1. Native accessors — raw Admin SDK instances

    2. Pipeline operations — subquery joins, bulk update/delete via Firebase 14 Enterprise:

    3. Change streams — ordered insert/update/delete events (cache invalidation, audit logs):

    4. Text search — full-text search via Cloud Firestore Enterprise text indexes:

    5. Geospatial search — radius search via Enterprise geo indexes:

    FCM Admin bridge (new)

    Server-side push notifications via HTTP v1 API. Lazy-initializes getAdminMessaging():

    All sendFcm* methods use sendEach() (not legacy sendMulticast()) and include automatic reactive cleanup of invalid tokens (messaging/registration-token-not-registered and messaging/invalid-argument errors mark tokens as invalid in the fcm_tokens collection).

    SSOT atomic writes bridge (new)

    Delegates to lib/services/firebase-service-manager.ts atomic helpers — single Firestore transaction, ACID, prevents read-modify-write race:

    Connection diagnostics

    Useful for /api/admin/firebase-health endpoints and CI smoke tests.

    firebase-service-manager (firebase-service-manager.ts)

    Provides 30+ cached Firebase operations using React 19 cache() for per-request deduplication. SSOT-aligned to the canonical collection shapes:

    Domain-specific SSOT helpers

    HelperSSOT collectionPurpose
    getCachedUserCreditBalanceusers/{userId}.credit_balanceCached fiat USD credit balance
    getCachedCreditTransactionsusers/{userId}.credit_transactions[]Credit transaction history
    getCachedLatestSubscriptionsubscription_ledgerLatest subscription (any status)
    getCachedActiveSubscriptionsubscription_ledgerActive subscription (ACTIVE + active)
    getCachedSubscriptionsDuesubscription_ledgerDue-for-payment batch (cron)
    getCachedPaymentTransactionpayment_transactionsBy order_reference
    getCachedWalletTransactionswallet_transactionsPer-user activity feed
    getCachedDeskOrderdesk_ordersCredit ↔ native-token conversion
    getCachedUserOrdersordersUser order history

    Legacy aliases preserved

    Build-time optimization

    All cached functions are only called when getAdminDb() returns real Firestore. During build (SSG), the mock Firestore methods (now including MockMessaging, MockStorage, MockAppCheck, MockRemoteConfig) return safe empty results. The cache() layer deduplicates within each request, reducing redundant Firestore reads by 3–7×.

    AI Logic bridge (new)

    Server-side Gemini 2.5/3.x text generation and chat via Firebase AI Logic. Requires optional dependency firebase-admin/vertexai + @google-cloud/vertexai. Without them, helpers return a clear not-supported error (no crash).

    For the client-side equivalent in the browser, use getFirebaseAIClient() from lib/firebase-client.ts:

    Firebase Hosting bridge (new)

    firebase-full mode supports Firebase Hosting as a production deployment target alongside k3s, Vercel, and Docker. Module-level helpers generate a canonical firebase.json for hosting rewrites/headers only — they do not register schedulers or ProcessConductor cron entries:

    The default rewrites map the canonical Ring routes:

    • /api/** → Cloud Function / App Hosting backend
    • /auth/** → Auth.js handlers
    • /trpc/** → tRPC (when present)
    • /tunnel/** → Tunnel WebSocket/SFE handler
    • /_actions/** → React 19 Server Actions
    • firebase-messaging-sw.js → Service Worker
    • ** → SPA fallback (/index.html)

    Default headers include Cache-Control: immutable for .js/.css, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, and Permissions-Policy.

    generateFirebaseJson / getRingHostingRewrites in FirebaseAdapter.ts = Hosting rewrites/headers only. Schedule ProcessConductor jobs with Cloud Scheduler against /api/cron/* — see below.

    ProcessConductor cron (Cloud Scheduler)

    Same HTTP contracts as Vercel and k3s — different trigger.

    LayerPath / artifact
    Registry SSOTlib/processes/registry.ts — PIPELINE_IDS + PIPELINE_REGISTRY (19 pipelines)
    Routesapp/api/cron/<name>/route.ts — GET/POST, fail-closed on Authorization: Bearer $CRON_SECRET
    Schedule catalogvercel.json → crons[] (UTC) — must list every cronPath
    LedgerProcessConductor.recordRun
    Admin labelslocales/*/modules/admin.json → processes.pipelines.*
    Human ops docAI-RING/scripts/PIPELINES.md § ProcessConductor cron pipelines

    Wire Google Cloud Scheduler

    1. 1

      Set CRON_SECRET in the Firebase-hosted / Cloud Run app environment (same secret you will send from Scheduler).

    2. 2

      Copy every { path, schedule } from root vercel.json crons[]. Full catalog (UTC) is also documented on Vercel deployment.

    3. 3

      For each entry, create a Cloud Scheduler job:

      • Frequency: the schedule string (UTC)
      • Target: https://<your-ring-host><path> (e.g. /api/cron/email-processor)
      • HTTP method: GET or POST (both accepted by Ring cron routes)
      • Header: Authorization: Bearer <CRON_SECRET>
    4. 4

      Smoke-test one job:

      bash
      
      curl -sS -H "Authorization: Bearer $CRON_SECRET" \
        "https://<your-ring-host>/api/cron/cleanup-usernames"

      Expect 200 when authorized; 401 when the secret is missing/wrong (fail-closed).

    5. 5

      Confirm Admin → Background Processes shows recorded runs. When adding a pipeline: update registry.ts + route + locale labels + append vercel.json (schedule catalog) + optional k8s/cronjob-*.yaml.

    Ops notes: forgejo-robot-gc supports ?dryRun=1 (classifies without deleting). forgejo-token-rotate reads FORGEJO_TOKEN_ROTATE_MAX_AGE_DAYS / FORGEJO_TOKEN_ROTATE_LIMIT. k3s contrast: per-job YAML under k8s/ curling an in-cluster URL — not a single inventory file.

    Build-time mocking (build-mock.server.ts)

    During Next.js static generation (NEXT_PHASE=phase-production-build), Firebase Admin calls are intercepted by mock services. Now includes 7 mock classes (up from 3):

    Mock classMethodsAdmin SDK type
    MockFirestorecollection, doc, get, set, update, delete, batch, runTransactionFirestore
    MockAuthgetUser, createUser, updateUser, deleteUser, listUsers, verifyIdTokenAuth
    MockDatabaseref, push, set, update, remove, once, on, orderByChild, ...Database
    MockMessagingsend, sendEach, sendAll, sendToTopic, subscribeToTopic, unsubscribeFromTopicMessaging
    MockStoragebucket, file, upload, getFiles, delete, getMetadata + defaultBucketStorage
    MockAppCheckcreateToken, verifyTokenAppCheck
    MockRemoteConfiggetTemplate, publishTemplate, rollback, listVersionsRemoteConfig

    This prevents ~22 redundant Firebase Admin initializations during SSG and reduces build time by approximately 31%.

    Environment variables

    Admin SDK (server-side)

    Client SDK (browser-side)

    isFcmConfigured() / validateFcmVapidKey() / getFcmVapidKey() in lib/firebase-client.ts require a non-placeholder NEXT_PUBLIC_FIREBASE_VAPID_KEY. Mismatched certificates fail at Firebase getToken — Ring does not classify key prefixes. Deep push map: Push notifications (FCM).

    Key differences between the two Firebase paths

    AspectAdmin SDK (firebase-admin.server.ts)Adapter (FirebaseAdapter.ts)
    CredentialsADC-first — cert() fallback when AUTH_FIREBASE_* presentADC-first — matches Admin SDK pattern
    Env varsOnly AUTH_FIREBASE_PROJECT_ID for ADC; all 3 for cert()Only AUTH_FIREBASE_PROJECT_ID for ADC
    Active inAll DB_BACKEND_MODE values (FCM always available)Only firebase-full
    Mock in postgres-primaryYes — all services returned as mocksNo — adapter not registered
    Import pathfirebase-admin/app, firebase-admin/firestore, firebase-admin/messaging, etc.Same (delegates via dynamic import)
    Versionv14 (named subpath imports)v14
    React 19 nativeglobalThis HMR-safe singleton + per-service observabilitycache()-wrapped lazy getters + async methods for use() + Suspense
    Enterprise featuresPipeline, change streams, text/geo, AI Logic (via Vertex AI)All of the above as adapter methods + module-level exports

    Authentication Architecture

    See-also: Auth.js v5 — Firebase Auth is not used.

    k8s-postgres-fcm Mode

    See-also: PostgreSQL-primary mode with FCM-only Firebase.

    Admin console

    Next-step: Admin → Background Processes labels for pipeline runs.

    Email AI-CRM

    Same-workflow: email-processor / email-analytics cron pipelines.

    typescript
    
    import { cert, initializeApp, type App } from 'firebase-admin/app'
    
    // globalThis-backed singleton prevents "already exists" errors on HMR
    const globalForAdmin = globalThis as typeof globalThis & {
      __RING_FIREBASE_ADMIN_APP__?: App
    }
    
    export function getAdminApp(): App {
      if (globalForAdmin.__RING_FIREBASE_ADMIN_APP__) {
        return globalForAdmin.__RING_FIREBASE_ADMIN_APP__
      }
      if (getApps().length > 0) {
        const app = getApps()[0]
        globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
        return app
      }
    
      // Check if we have explicit credentials for cert() fallback
      const hasExplicitCreds =
        !!process.env.AUTH_FIREBASE_PROJECT_ID &&
        !!process.env.AUTH_FIREBASE_CLIENT_EMAIL &&
        !!process.env.AUTH_FIREBASE_PRIVATE_KEY
    
      const appOptions = hasExplicitCreds
        ? { credential: cert({ /* cleaned env vars */ }) }
        : {} // ADC auto-detected from environment
    
      const app = initializeApp(appOptions)
      globalForAdmin.__RING_FIREBASE_ADMIN_APP__ = app
      return app
    }
    bash
    
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----\n"
    typescript
    
    import { getFirebaseClientApp, getFirebaseFirestoreClient } from '@/lib/firebase-client'
    
    // SSR-safe: returns undefined during server render
    const app = getFirebaseClientApp()
    const db = getFirebaseFirestoreClient()
    typescript
    
    import { isEmulatorEnabled, connectFirebaseClientEmulator } from '@/lib/firebase-client'
    
    if (isEmulatorEnabled()) {
      await connectFirebaseClientEmulator()
    }
    typescript
    
    // Production (Cloud Run, GKE, Cloud Functions):
    //   ADC auto-detects from the environment — no manual cert() needed.
    //   Only AUTH_FIREBASE_PROJECT_ID required.
    
    // Local dev / CI:
    //   Falls back to explicit cert() when credentials.clientEmail and
    //   credentials.privateKey are present in the backend config.
    typescript
    
    // Single document
    await db.findById('users', userId)
    // => { success, data: T | null, error? }
    
    // Query with filters, ordering, pagination
    await db.query({
      collection: 'entities',
      filters: [{ field: 'status', operator: 'eq', value: 'active' }],
      orderBy: [{ field: 'createdAt', direction: 'desc' }],
      pagination: { limit: 20, offset: 0 },
    })
    
    // Mutations
    await db.create('users', data)
    await db.update('users', id, { name: 'New' })
    await db.delete('users', id)
    
    // Transactions
    await db.transaction(async (txn) => { ... })
    typescript
    
    const native = await adapter.getNativeFirestore()
    const auth = await adapter.getNativeAuth()
    const messaging = await adapter.getNativeMessaging()
    const storage = await adapter.getNativeStorage()
    const appCheck = await adapter.getNativeAppCheck()
    typescript
    
    const result = await adapter.runPipeline('orders', [
      { where: { field: 'status', op: '==', value: 'paid' } },
      { aggregate: { sum: { field: 'amount' }, as: 'total' } },
      { limit: 10 },
    ])
    typescript
    
    const { data } = await adapter.onCollectionChange('subscription_ledger', (event) => {
      if (event.type === 'modified') {
        publishToChannel(event.doc.id, 'subscription:update', event.doc)
      }
    })
    typescript
    
    const { data } = await adapter.textSearch('entities', 'organic coffee', {
      field: 'description',
      limit: 20,
    })
    typescript
    
    const { data } = await adapter.geoSearch('entities',
      { latitude: 49.44, longitude: 32.06 },
      50, // radius in km
      { field: 'location', limit: 20 },
    )
    typescript
    
    // Single FCM message
    await adapter.sendFcmMessage({
      token: 'fcm-registration-token',
      notification: { title: 'Hello', body: 'World' },
      webpush: { fcmOptions: { link: 'https://ring-platform.org/notifications' } },
    })
    
    // Multi-token fan-out to a specific user (auto-cleans dead tokens)
    await adapter.sendFcmToUser(userId, { title: 'New message', body: 'Text' },
      { route: '/messages/123' },
    )
    
    // Topic broadcast
    await adapter.sendFcmToTopic('global-announcements', { title: 'Maintenance', body: 'Tonight 2-4 AM' })
    
    // Token validation
    const { data } = await adapter.validateFcmToken(token)
    if (!data?.valid) { /* remove from DB */ }
    
    // Reactive cleanup
    await adapter.cleanupInvalidFcmTokens(['dead-token-1', 'dead-token-2'])
    typescript
    
    // Atomic credit balance adjust (fiat USD)
    await adapter.creditBalanceAdjust(userId, -10, { id: 'ct_xxx', description: 'Membership fee' })
    
    // Atomic subscription status transition (ledger + user doc)
    await adapter.subscriptionStatusUpdate(subId, 'cancelled',
      { membership: { tier: 'SUBSCRIBER' } },
      { cancelled_at: Date.now(), auto_renew: false },
    )
    
    // Atomic payment transaction status append (status_history prevention)
    await adapter.paymentStatusAppend(orderRef, 'paid', { processor_payload: { ... } })
    typescript
    
    const diagnostics = await adapter.diagnoseConnection()
    // => { backendMode, firebaseConfigured, services: { firestore: true, ... }, errors: [] }
    typescript
    
    getUserCreditTransactions: getCachedCreditTransactions, // legacy → SSOT
    getActiveSubscriptions: getCachedSubscriptionsDue,       // legacy → SSOT
    getUserOrders: getCachedUserOrders,                      // legacy → SSOT
    typescript
    
    import { aiGenerateText, aiChatCompletion } from '@/lib/database/adapters/FirebaseAdapter'
    
    // Text generation
    const result = await aiGenerateText({
      prompt: 'What is the Ring Platform?',
      model: 'gemini-2.5-flash',
      temperature: 0.7,
      maxOutputTokens: 1024,
    })
    
    if (result.success) {
      console.log(result.data!.text, result.data!.usage)
    }
    
    // Multi-turn chat
    const chatResult = await aiChatCompletion({
      model: 'gemini-2.5-flash',
      messages: [
        { role: 'system', content: 'You are a Ring Platform expert.' },
        { role: 'user', content: 'How do I set up firebase-full?' },
      ],
    })
    typescript
    
    import { getFirebaseAIClient } from '@/lib/firebase-client'
    
    const ai = await getFirebaseAIClient()
    if (ai) {
      const result = await ai.getGenerativeModel({ model: 'gemini-2.5-flash' }).generateContent('...')
    }
    typescript
    
    import { generateFirebaseJson, getRingHostingRewrites } from '@/lib/database/adapters/FirebaseAdapter'
    import { writeFile } from 'fs/promises'
    
    const config = generateFirebaseJson({
      public: 'out',
      rewrites: getRingHostingRewrites(), // /api/** → /api, /auth/** → /auth, ** → /index.html
    })
    
    await writeFile('firebase.json', JSON.stringify(config, null, 2))
    // Then: firebase deploy --only hosting
    bash
    
    # For ADC (Cloud Run / GKE / Cloud Functions — recommended):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    
    # For explicit cert() fallback (local dev / CI — all three required):
    AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
    AUTH_FIREBASE_CLIENT_EMAIL=your_firebase_client_email
    AUTH_FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
    
    # Optional:
    FIREBASE_DATABASE_URL=https://your-project-default-rtdb.firebaseio.com
    FIREBASE_STORAGE_BUCKET=your-project.appspot.com
    FIREBASE_PRIVATE_KEY_ID=your_firebase_private_key_id
    FIREBASE_CLIENT_ID=your_firebase_client_id
    FIREBASE_FIRESTORE_DEBUG=true
    bash
    
    NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
    NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain
    NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_firebase_project_id
    NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket
    NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
    NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
    NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-YCZKPV315E
    NEXT_PUBLIC_FIREBASE_VAPID_KEY=your_vapid_key
    # ↑ Cloud Messaging → Web Push certificates (same project). Rebuild after rotate.
    NEXT_PUBLIC_FIREBASE_USE_EMULATOR=1  # Enables Firebase Local Emulator Suite auto-connect
    1. Docs
    2. /Backend Services
    3. /Firebase Integration

    Updated Aug 16, 202613 min listen

    1. Docs
    2. /Backend Services
    3. /Firebase Integration

    Updated Aug 16, 202613 min listen

    1. Docs
    2. /Backend Services
    3. /Firebase Integration

    Updated Aug 16, 202613 min listen