Eko v2 Rollout User Checklist

Audience: Solo operator (Jonathan). Scope: Only items requiring human action on external services — API keys, accounts, config, DNS, manual verification. Code tasks are handled in branches and are out of scope.


Phase 1: Pre-Deploy (Accounts & Credentials)

1.1 Supabase

  • Create production Supabase project (or confirm existing)
  • Copy credentials from Dashboard > Settings > API:
Env VarWhere to get it
SUPABASE_URLDashboard > Settings > API > Project URL
SUPABASE_ANON_KEYDashboard > Settings > API > anon public key
SUPABASE_SERVICE_ROLE_KEYDashboard > Settings > API > service_role key
DATABASE_URLDashboard > Settings > Database > Connection string (Transaction mode via Supavisor)
  • Enable Google OAuth provider: Dashboard > Authentication > Providers > Google
    • Requires Google Cloud OAuth client ID + secret
  • Enable Apple OAuth provider: Dashboard > Authentication > Providers > Apple
    • Requires Apple Services ID + key from Apple Developer portal
  • Configure Auth redirect URLs: add https://app.eko.day/** to allowed redirect URLs
  • Confirm RLS is enabled on all tables (migrations create policies, but verify)

1.2 Upstash Redis

Env VarWhere to get it
UPSTASH_REDIS_REST_URLUpstash Console > Database > REST API > Endpoint
UPSTASH_REDIS_REST_TOKENUpstash Console > Database > REST API > Token

1.3 AI Providers

Env VarWhere to get it
AI_PROVIDERSet to anthropic (default)
ANTHROPIC_API_KEYAnthropic Console > API Keys
OPENAI_API_KEYOpenAI Platform > API Keys
GOOGLE_API_KEYRequired — Google AI Studio > API Keys (needed for fact validation pipeline)
AI_MODEL_ANTHROPIC(optional) Override default model, e.g. claude-haiku-4-5
AI_MODEL_OPENAI(optional) Override default model, e.g. gpt-4o-mini
AI_MODEL_GOOGLE(optional) Override default model, e.g. gemini-2.0-flash
ANTHROPIC_DAILY_SPEND_CAP_USD(optional) Default 5.00 — daily spend cap before fallback to GPT-4o-mini
OPUS_ESCALATION_ENABLED(optional) Default false — enable Opus 4.6 escalation for complex cases
OPUS_MAX_DAILY_CALLS(optional) Default 20 — max Opus calls/day

1.4 News APIs (Fact Engine)

  • Obtain a news API key (e.g., NewsAPI.org, GNews, or similar)
  • (Optional) Google News API key if using Google's news endpoint
Env VarWhere to get it
NEWS_API_KEYYour news API provider dashboard
GOOGLE_NEWS_API_KEY(optional) Google API Console

Tuning params (all have sane defaults):

Env VarDefaultPurpose
NEWS_INGESTION_INTERVAL_MINUTES15How often to poll for news
FACT_EXTRACTION_BATCH_SIZE10Articles processed per extraction run
VALIDATION_MIN_SOURCES2Minimum sources for fact validation
NOTABILITY_THRESHOLD0.6Score threshold for publishing facts
EVERGREEN_DAILY_QUOTA20Max evergreen facts generated/day
EVERGREEN_ENABLEDfalseEnable evergreen fact generation

1.5 Stripe (Billing)

  • Create Stripe account or confirm existing at dashboard.stripe.com
  • Create 5 Price objects in Stripe (Products > Create product > Add price):
    • Base plan (monthly)
    • Pro plan (monthly)
    • Team plan (monthly)
    • v2: Plus plan — monthly
    • v2: Plus plan — annual
  • Create a webhook endpoint pointing to https://app.eko.day/api/webhooks/stripe
    • Events to listen for: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed
  • Copy credentials:
Env VarWhere to get it
STRIPE_SECRET_KEYStripe Dashboard > Developers > API Keys > Secret key
STRIPE_WEBHOOK_SECRETStripe Dashboard > Developers > Webhooks > Signing secret
STRIPE_PRICE_BASEPrice ID from Stripe (starts with price_)
STRIPE_PRICE_PROPrice ID from Stripe
STRIPE_PRICE_TEAMPrice ID from Stripe
STRIPE_PRICE_PLUS_MONTHLYv2 new — Price ID for Plus monthly
STRIPE_PRICE_PLUS_ANNUALv2 new — Price ID for Plus annual

Note: .env.example is currently missing STRIPE_PRICE_PLUS_MONTHLY and STRIPE_PRICE_PLUS_ANNUAL. A code PR should add them.

1.6 Email (Resend)

  • Create Resend account at resend.com
  • Verify sending domain (e.g., eko.day) in Resend dashboard
  • Copy credentials:
Env VarWhere to get it
EMAIL_PROVIDERSet to resend to enable
RESEND_API_KEYResend Dashboard > API Keys
RESEND_FROM_EMAILVerified sender address, e.g. Eko <alerts@eko.day>

1.7 Error Tracking (Sentry)

  • Create Sentry project at sentry.io (or confirm existing)
  • Copy DSN:
Env VarWhere to get it
ERROR_TRACKING_PROVIDERSet to sentry
SENTRY_DSNSentry > Project Settings > Client Keys (DSN)
SENTRY_AUTH_TOKEN(optional, for source maps) Sentry > Settings > Auth Tokens
SENTRY_ENVIRONMENT(optional) Defaults to NODE_ENV

1.8 Enrichment (Optional)

  • (Optional) Brandfetch API key for brand logos
  • (Optional) People Data Labs API key for company firmographics
Env VarWhere to get it
BRANDFETCH_API_KEYbrandfetch.com dashboard
PDL_API_KEYpeopledatalabs.com dashboard

1.9 Internal Secrets

  • Generate a strong random CRON_SECRET (e.g., openssl rand -hex 32)
  • Set admin email allowlist
Env VarValue
CRON_SECRETRandom 32+ char hex string
ADMIN_EMAIL_ALLOWLISTComma-separated admin emails
LOG_LEVELinfo for production
NODE_ENVproduction

1.10 Application URLs

Env VarProduction Value
NEXT_PUBLIC_APP_URLhttps://app.eko.day
NEXT_PUBLIC_PUBLIC_URLhttps://eko.day

Phase 2: Database Migrations

2.1 Apply v2 Migrations (0090-0113)

All 24 migrations must be applied in order. Run via Supabase CLI or Dashboard SQL editor.

#FilePurpose
00900090_ai_model_tiers.sqlAI model tier config table
00910091_ai_model_tier_enum.sqlAI model tier enum type
00920092_fact_engine_enums_and_taxonomy.sqlFact engine enums & topic taxonomy
00930093_fact_records.sqlCore fact_records table
00940094_stories_and_news_sources.sqlStories & news sources tables
00950095_card_interactions_and_ingestion.sqlCard interactions & ingestion runs
00960096_plan_updates_and_seed_taxonomy.sqlPlan updates & taxonomy seed data
01000100_score_column.sqlScore column on fact records
01010101_category_expansion.sqlExpanded topic categories
01020102_card_format_free_text.sqlCard format free text column
01030103_score_disputes.sqlScore dispute tracking
01040104_reward_milestones.sqlReward milestone tables
01050105_challenge_formats.sqlChallenge format definitions
01060106_challenge_format_junctions.sqlChallenge format junction tables
01070107_interactions_format_tracking.sqlInteraction format tracking
01080108_seed_challenge_formats.sqlSeed data for challenge formats
01090109_challenge_sessions.sqlChallenge session management
01100110_challenge_format_rls.sqlRLS policies for challenge formats
01110111_drop_page_tracking_tables.sqlDrop legacy page tracking tables
01120112_drop_brand_auxiliary_tables.sqlDrop legacy brand auxiliary tables
01130113_drop_misc_legacy.sqlDrop misc legacy tables

Gap note: Migrations 0097-0099 do not exist. This is intentional (reserved numbers).

  • Back up production database before running migrations
  • Apply migrations 0090-0096 (core v2 infrastructure)
  • Apply migrations 0100-0110 (v2 features & challenge system)
  • Apply migrations 0111-0113 (legacy table cleanup) — destructive, run last
  • Verify all RLS policies are active: SELECT tablename, policyname FROM pg_policies;
  • Run bun run db:types to regenerate TypeScript types from updated schema
  • Verify taxonomy seed data was applied (migration 0096 seeds topic categories)
  • Verify challenge format seed data was applied (migration 0108)
  • (Optional) Seed feature flags via admin dashboard or SQL:
    INSERT INTO feature_flags (key, enabled, description) VALUES
      ('ai_provider_anthropic', true, 'Enable Anthropic as AI provider'),
      ('ai_provider_openai', true, 'Enable OpenAI as AI provider'),
      ('ai_provider_google', false, 'Enable Google as AI provider');
    

    Feature flags are fail-open — if rows don't exist, providers default to enabled. Seeding is recommended for explicit control.


Phase 3: Deploy

3.1 Vercel (Web + Admin + Public)

  • Connect GitHub repo to Vercel project
  • Configure 3 Vercel projects (or mono-project with directory settings):
    • apps/webapp.eko.day
    • apps/adminadmin.eko.day
    • apps/publiceko.day
  • Add all environment variables from Phase 1 to each Vercel project's settings
  • Set NODE_ENV=production in Vercel environment settings

3.2 Register v2 Cron Routes in vercel.json

Current state: apps/web/vercel.json has only 5 legacy cron routes. The following 8 v2 fact-engine cron routes exist in code but are NOT registered — they won't fire until added:

RouteSuggested SchedulePurpose
/api/cron/ingest-news*/15 * * * * (every 15 min)Poll news APIs for new articles
/api/cron/cluster-sweep*/30 * * * * (every 30 min)Cluster similar articles into stories
/api/cron/import-facts*/15 * * * * (every 15 min)Extract facts from clustered articles
/api/cron/generate-evergreen0 */6 * * * (every 6 hours)Generate evergreen educational facts
/api/cron/validation-retry0 */2 * * * (every 2 hours)Retry failed fact validations
/api/cron/archive-content0 3 * * * (3 AM daily)Archive old content
/api/cron/topic-quotas0 0 * * * (midnight daily)Reset/enforce topic quotas
/api/cron/daily-digest0 8 * * * (8 AM daily)Send daily digest emails
  • Add these 8 routes to apps/web/vercel.json (code change needed — PR in progress or create one)
  • Verify all 13 cron routes are registered after deploy:
    • Existing: payment-reminders, payment-escalation, monthly-usage-report, account-anniversaries, daily-cost-report
    • New v2: ingest-news, cluster-sweep, import-facts, generate-evergreen, validation-retry, archive-content, topic-quotas, daily-digest

3.3 Fly.io Workers

Three Bun-based queue consumer workers need Fly.io deployment:

WorkerApp DirectoryPurpose
worker-ingestapps/worker-ingestNews article ingestion & clustering
worker-factsapps/worker-factsAI fact extraction from articles
worker-validateapps/worker-validateMulti-tier fact validation

For each worker:

  • Create Fly.io app: fly apps create eko-worker-ingest (repeat for each)
  • Create fly.toml in each worker directory (no fly.toml exists yet)
  • Set secrets on each Fly app:
    fly secrets set \
      SUPABASE_URL=... \
      SUPABASE_ANON_KEY=... \
      SUPABASE_SERVICE_ROLE_KEY=... \
      DATABASE_URL=... \
      UPSTASH_REDIS_REST_URL=... \
      UPSTASH_REDIS_REST_TOKEN=... \
      ANTHROPIC_API_KEY=... \
      OPENAI_API_KEY=... \
      CRON_SECRET=... \
      NODE_ENV=production \
      -a eko-worker-ingest
    
  • Deploy each worker: fly deploy -a eko-worker-ingest
  • Verify health endpoint responds: curl https://eko-worker-ingest.fly.dev/health

3.4 DNS Configuration

Record TypeNameValueService
CNAMEapp.eko.daycname.vercel-dns.comVercel (web app)
CNAMEadmin.eko.daycname.vercel-dns.comVercel (admin)
A/CNAMEeko.dayVercel IP or CNAMEVercel (public site)
  • Add DNS records at your domain registrar / Cloudflare
  • Add custom domains in Vercel project settings
  • Verify SSL certificates are provisioned (Vercel auto-provisions via Let's Encrypt)

Phase 4: Post-Deploy Verification

4.1 Service Health Checks

  • Web app loads at https://app.eko.day — login works (Google/Apple OAuth)
  • Admin dashboard loads at https://admin.eko.day — admin email can log in
  • Public site loads at https://eko.day
  • Stripe webhook test event succeeds (Stripe Dashboard > Webhooks > Send test event)
  • Cron jobs fire on schedule (Vercel Dashboard > Crons > verify next runs)
  • Workers respond to health checks on Fly.io

4.2 Fact Engine Smoke Test

  • Trigger manual news ingestion: curl -H "Authorization: Bearer $CRON_SECRET" https://app.eko.day/api/cron/ingest-news
  • Verify articles appear in ingestion_runs table
  • Trigger fact extraction: curl -H "Authorization: Bearer $CRON_SECRET" https://app.eko.day/api/cron/import-facts
  • Verify facts appear in fact_records table with status pending_validation
  • Trigger validation: check worker-validate logs for processing
  • Verify at least one fact reaches validated status

4.3 Subscription Flow

  • Create a test subscription via Stripe test mode
  • Verify user_subscriptions table updates
  • Verify Eko+ features unlock in the app
  • Test upgrade from Free to Plus (monthly and annual)

4.4 Error Tracking

  • Trigger a test error and verify it appears in Sentry
  • Verify source maps are working (stack traces show original TypeScript)

Phase 5: Ongoing Operations

5.1 Cost Monitoring

  • Set up Anthropic usage alerts at console.anthropic.com
  • Set up OpenAI usage alerts at platform.openai.com
  • Set up Stripe billing alerts
  • Monitor daily cost report emails (from daily-cost-report cron)
  • Review ANTHROPIC_DAILY_SPEND_CAP_USD — adjust from default $5.00 if needed

5.2 Feature Flag Management

Key feature flags to monitor/adjust via admin dashboard:

Flag KeyDefaultPurpose
ai_provider_anthropictrue (fail-open)Kill switch for Anthropic
ai_provider_openaitrue (fail-open)Kill switch for OpenAI
ai_provider_googlefalseEnable Google AI (not yet integrated)
inline_diffstrueLegacy inline diff feature

5.3 Content Moderation

  • Set up a process to review published facts via admin dashboard
  • Configure NOTABILITY_THRESHOLD (default 0.6) — increase to be more selective
  • Monitor topic distribution via topic-quotas cron output

5.4 Backup & Recovery

  • Enable Supabase Point-in-Time Recovery (PITR) on Pro plan
  • Document rollback procedure for migrations 0111-0113 (destructive — no auto-rollback)

Quick Reference: Full Environment Variable Checklist

All variable names match packages/config/src/index.ts envSchema exactly.

Required

Env VarService
SUPABASE_URLSupabase
SUPABASE_ANON_KEYSupabase
SUPABASE_SERVICE_ROLE_KEYSupabase
CRON_SECRETInternal
Env VarService
DATABASE_URLSupabase (Supavisor pooler)
UPSTASH_REDIS_REST_URLUpstash
UPSTASH_REDIS_REST_TOKENUpstash
ANTHROPIC_API_KEYAnthropic
OPENAI_API_KEYOpenAI
GOOGLE_API_KEYGoogle AI (validation pipeline)
STRIPE_SECRET_KEYStripe
STRIPE_WEBHOOK_SECRETStripe
STRIPE_PRICE_BASEStripe
STRIPE_PRICE_PROStripe
STRIPE_PRICE_TEAMStripe
STRIPE_PRICE_PLUS_MONTHLYStripe (v2 new)
STRIPE_PRICE_PLUS_ANNUALStripe (v2 new)
RESEND_API_KEYResend
RESEND_FROM_EMAILResend
SENTRY_DSNSentry

Optional / Tuning

Env VarDefaultService
AI_PROVIDERanthropicConfig
AI_MODEL_ANTHROPIC(auto)Config
AI_MODEL_OPENAI(auto)Config
AI_MODEL_GOOGLE(auto)Config
ANTHROPIC_DAILY_SPEND_CAP_USD5.00Config
OPUS_ESCALATION_ENABLEDfalseConfig
OPUS_MAX_DAILY_CALLS20Config
NEWS_API_KEYNews provider
GOOGLE_NEWS_API_KEYGoogle News
NEWS_INGESTION_INTERVAL_MINUTES15Config
FACT_EXTRACTION_BATCH_SIZE10Config
VALIDATION_MIN_SOURCES2Config
NOTABILITY_THRESHOLD0.6Config
EVERGREEN_DAILY_QUOTA20Config
EVERGREEN_ENABLEDfalseConfig
CRON_BATCH_SIZE100Config
EMAIL_PROVIDERnoneConfig
ERROR_TRACKING_PROVIDERnoneConfig
EMAIL_ENABLEDfalseConfig (legacy)
SENTRY_ENABLEDfalseConfig (legacy)
BRANDFETCH_API_KEYBrandfetch
PDL_API_KEYPeople Data Labs
ADMIN_EMAIL_ALLOWLISTConfig
LOG_LEVELinfoConfig
NODE_ENVdevelopmentConfig
NEXT_PUBLIC_APP_URLhttps://app.eko.dayConfig
NEXT_PUBLIC_PUBLIC_URLhttps://eko.dayConfig
PORT8080Workers
SENTRY_ENVIRONMENT(NODE_ENV)Config