* fix(docs): correct variant count from 4 to 5 Updated overview.mdx and architecture.mdx to reflect all 5 platform variants (World Monitor, Tech Monitor, Happy Monitor, Finance Monitor, Commodity Monitor). Previously only 2 were listed in the table and the text said "four". * fix(docs): correct all numerical inaccuracies across documentation Updated counts verified against actual codebase: - AI datacenters: 111 → 313 - Undersea cables: 55 → 86 - Map layers: 45 → 49 - News sources: 80+ → 344 - Service domains: 22 → 24 - CII countries: 20 → 24 - Military bases: 210/220+ → 226 - Strategic ports: 61/83 → 62 - Airports: 30/107 → 111 - Chokepoints: 6/8 → 9 - Signal entities: 100+/600+ → 66 - Variant count: four → five (added Commodity Monitor) * docs(overview): add Happy, Finance, and Commodity Monitor sections Added detailed documentation sections for the three previously undocumented platform variants, including layers, panels, and news categories for each. * docs(features): document 13 previously undocumented features Added: trade routes, FIRMS fire detection, webcam surveillance, country brief, aviation intelligence panel, climate anomalies, displacement tracking, Gulf economies, WTO trade policy, central banks & BIS, market watchlist, NOTAM closure detection, and offline ML capabilities. * docs: standardize terminology, add cross-references, fix stale refs Phase 4: Renamed "News Importance Scoring" to "Headline Scoring", "Signal Correlation" to "Cross-Stream Correlation". Added cross-refs between CII/convergence, CORS/API-key, maritime/finance chokepoints. Deduplicated Population Exposure content. Clarified hotspot vs focal point distinction. Phase 5: Rewrote daily_stock_analysis as historical context. Added legacy note for api/*.js files. Added OREF 24h rolling history boost and GPS jamming classification thresholds to algorithms.mdx. Fixed orbital-surveillance tier table contradictions. Phase 6: Fixed orphaned markdown separator in orbital-surveillance. * fix(docs): catch remaining stale numbers in secondary doc files Fixed stale counts in data-sources.mdx, strategic-risk.mdx, documentation.mdx, ai-intelligence.mdx, PRESS_KIT.md, and roadmap-pro.md that were missed in the initial pass. * fix(docs): address review findings from fresh-eyes pass - desktop-app.mdx: "four variants" → "five", "22 services" → "24" - infrastructure-cascade.mdx: chokepoints 8 → 9, node total 279 → 350 - data-sources.mdx: chokepoints 8 → 9 - overview.mdx: remove duplicated intro sentence - getting-started.mdx: fix stale file tree comments (AI clusters, airports, entities)
47 KiB
World Monitor Pro — Implementation Roadmap
Context
The /pro landing page promises features across 4 tiers (Free, Pro, API, Enterprise) but almost nothing beyond the marketing page exists. Current state:
- Convex: bare —
registrations+counterstables only - Auth: none — no Clerk, no sessions. Desktop uses manual
WORLDMONITOR_API_KEYin keychain - Payments: none — no Stripe
- Gating: UI-only on desktop (6 panels, 3 map layers). No server-side enforcement.
api/_api-key.jsvalidates against staticWORLDMONITOR_VALID_KEYSenv var - User dashboard: none
- API tier: none (marketed as separate product)
- Delivery channels: none (Slack/Telegram/Discord/WhatsApp/Email)
- AI briefings: none (LLM infra exists via Groq but no scheduled briefs)
- Equity research: basic quotes only — no financials, analyst targets, valuation metrics
Key architectural constraint: main app is vanilla TS + Vite (NOT React). Only pro-test/ landing page is React. Clerk must use @clerk/clerk-js headless SDK.
Recommended MVP scope: Phases 0–4 + tasks 5.1, 5.2 = monetization MVP. Defer Phase 6 XL features until revenue validates demand.
Dependency Graph
Phase 0 (Decisions)
├──→ Phase 1 (Auth) ────┐
└──→ Phase 2 (Schema) ──┤
├──→ Phase 3 (Payments) ──→ Phase 4 (Gating)
│ │
│ ┌──────┼──────┐
│ ▼ ▼ ▼
└──────────────────→ Phase 5 Phase 6 Phase 7
(Dashboard) (Pro) (API)
│
▼
Phase 8 (Enterprise)
Critical path: Decisions → Auth + Schema (parallel) → Payments → Gating → everything else
Summary
| Phase | P0 | P1 | P2 | P3 | Total |
|---|---|---|---|---|---|
| 0: Decisions | 3 | — | — | — | 3 |
| 1: Auth | 2 | 2 | — | — | 4 |
| 2: Schema | 2 | 1 | 1 | — | 4 |
| 3: Payments | 3 | 2 | 1 | — | 6 |
| 4: Gating | 2 | 2 | — | — | 4 |
| 5: Dashboard | — | 2 | 3 | — | 5 |
| 6: Pro Features | — | 5 | 3 | — | 8 |
| 7: API Tier | — | 2 | 2 | — | 4 |
| 8: Enterprise | — | — | — | 10 | 10 |
| Total | 12 | 16 | 10 | 10 | 48 |
GitHub Issues
Phase 0: Foundational Decisions
Issue #0.1: Select authentication provider
Title: decision: auth provider — Clerk (@clerk/clerk-js headless) vs Convex Auth
Labels: decision, auth, P0
Priority: P0 | Size: S | Dependencies: None
Description: Evaluate and select an authentication provider for World Monitor Pro.
Options:
- Clerk (recommended) — first-class Convex integration, handles email/social login, webhook sync to Convex.
@clerk/clerk-jsheadless SDK for vanilla TS app,@clerk/clerk-reactfor/proReact page. - Convex Auth — built-in, fewer moving parts, but newer and less battle-tested.
- Supabase Auth — battle-tested but adds another infra layer on top of Convex.
Key constraint: Main app is vanilla TS + Vite (NOT React). Auth SDK must support headless DOM mounting (mountSignIn() / mountSignUp()).
Acceptance criteria:
- Decision documented with rationale
- Prototype: sign-in flow working in vanilla TS with chosen provider
- Verify Convex webhook sync works (user created in Clerk → user appears in Convex)
Issue #0.2: Select payment provider
Title: decision: payment provider — Stripe Checkout (hosted) vs embedded
Labels: decision, payments, P0
Priority: P0 | Size: S | Dependencies: None
Description: Select payment processing approach.
Recommendation: Stripe Checkout (hosted). Simpler than embedded, handles SCA/3DS automatically, less frontend code. Stripe Customer Portal for billing management.
Acceptance criteria:
- Stripe account configured with test mode
- Decision documented: hosted vs embedded checkout
Issue #0.3: API tier architecture decision
Title: decision: API tier architecture — separate Stripe products, independent of Pro plan
Labels: decision, api-tier, P0
Priority: P0 | Size: S | Dependencies: None
Description: The marketing page states API is "separate from Pro — use both or either." Define the entitlement model.
Decision points:
- Separate Stripe products: Pro Monthly/Annual + API Starter + API Business
- A user can have Pro (dashboard features) without API access, or API access without Pro
- Single
entitlementsprojection table derives access from all active subscriptions - Rate limits per
rateLimitTier, not per product
Acceptance criteria:
- Entitlement matrix documented (which endpoints are free/pro/api-only)
- Schema for
entitlementsprojection table designed
Phase 1: Authentication (Weeks 1–2)
Issue #1.1: Clerk + Convex integration
Title: feat(auth): Clerk + Convex integration — users table, webhook sync
Labels: auth, backend, infra, P0
Priority: P0 | Size: M | Dependencies: #0.1
Description: Set up Clerk as the authentication provider and wire it into Convex via webhook.
Implementation:
- Install
@clerk/clerk-js(vanilla TS main app) +@clerk/clerk-react(pro-test React page) - Add
userstable toconvex/schema.ts(see schema in Phase 2) - Create Clerk webhook handler as Convex HTTP action:
user.created→ create user in Convex withclerkId,email,name,plan: "free"user.updated→ sync email/name changesuser.deleted→ anonymize/tombstone user records (NOT hard delete audit/billing)
- Configure environment variables:
VITE_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY,CLERK_WEBHOOK_SECRET
Key files:
convex/schema.ts— add users tableconvex/clerk-webhook.ts— new HTTP action.env.example— add Clerk env vars
Acceptance criteria:
- User signs up via Clerk → user document created in Convex
userstable - User updates profile in Clerk → Convex user updated
- Webhook signature verified (reject unsigned/invalid requests)
- Automated: Clerk webhook integration test
Issue #1.2: Sign-in/sign-up UI in vanilla TS dashboard
Title: feat(auth): sign-in/sign-up UI in vanilla TS dashboard (clerk-js headless)
Labels: auth, frontend, P0
Priority: P0 | Size: M | Dependencies: #1.1
Description:
Add authentication UI to the main vanilla TS dashboard using Clerk's headless @clerk/clerk-js SDK.
Implementation:
- Initialize
Clerkinstance in app entry point - Use
clerk.mountSignIn(element)/clerk.mountSignUp(element)for auth modals - Add user avatar + dropdown to existing navbar (sign out, account, billing links)
- Expose
currentUserand user entitlements via a service module (src/services/auth.ts) - Update locked panel CTA from "Join Waitlist" to "Sign Up / Sign In"
Key files:
src/main.ts— Clerk initializationsrc/services/auth.ts— new auth servicesrc/components/Panel.ts— update locked panel CTA (line ~300)src/locales/en.json— updatepremium.joinWaitlistto "Sign In to Unlock"
Risk: @clerk/clerk-js headless is less documented than React SDK. Prototype mountSignIn() early to validate the approach.
Acceptance criteria:
- Sign in / sign up modal works in vanilla TS app
- User avatar + dropdown in navbar
- Locked panel CTA says "Sign In to Unlock" (or "Upgrade to Pro" if already signed in as free)
- Auth state persists across page refreshes
Issue #1.3: Tauri desktop auth flow
Title: feat(auth): Tauri desktop auth flow — PKCE + deep link callback
Labels: auth, desktop, P1
Priority: P1 | Size: L | Dependencies: #1.1
Description: Implement Clerk auth flow for the Tauri desktop app with proper session persistence.
Implementation:
- Register
worldmonitor://auth/callbackdeep link URI scheme in Tauri config - Use PKCE OAuth flow (Clerk supports this)
- On successful callback, store Clerk session token in macOS Keychain via existing
setSecret()pattern - Token lifecycle: refresh on app foreground, auto-refresh if <5min remaining
- Logout: clear keychain entry +
clerk.signOut()+ invalidate cached entitlements - Fallback: if deep link fails, show one-time code flow (email-based)
Key files:
src-tauri/tauri.conf.json— register deep linksrc-tauri/capabilities/default.json— add deep-link capabilitysrc/services/runtime-config.ts— existinggetSecretState/setSecret
Risk: Tauri WKWebView has known limitations. Use system browser for OAuth callback, pass token back via deep link.
Acceptance criteria:
- Sign in works on macOS desktop app
- Session persists across app restarts (keychain)
- Token auto-refreshes
- Sign out clears all cached state
Issue #1.4: Migrate waitlist registrations to users table
Title: feat(auth): migrate waitlist registrations → users table
Labels: auth, migration, P1
Priority: P1 | Size: M | Dependencies: #1.1
Description:
Migrate existing Convex registrations table entries to the new users table.
Migration playbook:
- Dry-run: migrate to staging Convex first, validate counts match
- Dedupe: normalize emails, merge duplicate registrations by
normalizedEmail - Consent: existing Turnstile-verified registrations have implicit consent; send "your account is ready" email with opt-out link via Resend
- Create Clerk accounts: use Clerk Admin API to create user accounts for each registration
- Preserve data: copy
referralCode,referralCount,source,appVersion - Rollback: keep
registrationstable intact, only deprecate after 30-day validation period - Validation: post-migration script compares
registrationscount vsuserscount, flags mismatches
Acceptance criteria:
- All waitlist emails have corresponding
usersentries - Referral codes and counts preserved
- "Account ready" emails sent via Resend
registrationstable untouched (rollback safety)- Dry-run report shows 0 mismatches
Phase 2: Convex Schema Expansion (Weeks 1–2, parallel with Phase 1)
Issue #2.1: Core schema — users, subscriptions, entitlements, apiKeys, usage, savedViews, alertRules
Title: feat(backend): users/subscriptions/entitlements/apiKeys/usage/savedViews/alertRules schema
Labels: backend, convex, P0
Priority: P0 | Size: M | Dependencies: #0.1, #0.3
Description: Design and implement the full Convex schema for Pro features.
Schema:
// New tables alongside existing registrations + counters
users: {
clerkId: string (indexed),
email: string (indexed),
name: string,
stripeCustomerId?: string (indexed),
referralCode: string (indexed),
referralCount: number,
createdAt: number,
updatedAt: number,
}
subscriptions: {
userId: Id<"users"> (indexed),
stripeSubscriptionId: string (indexed),
product: "pro" | "api_starter" | "api_business",
status: "active" | "past_due" | "canceled" | "trialing",
currentPeriodStart: number,
currentPeriodEnd: number,
cancelAtPeriodEnd: boolean,
createdAt: number,
}
entitlements: {
userId: Id<"users"> (indexed, unique),
dashboardTier: "free" | "pro",
apiTier: "none" | "starter" | "business",
rateLimitTier: "free_anon" | "free_authed" | "pro" | "api_starter" | "api_business",
features: string[], // ["equity_research", "ai_briefs", "saved_views", ...]
derivedFrom: Id<"subscriptions">[],
computedAt: number,
}
// Derived projection — recomputed on every subscription change
// Single source of truth for ALL gating decisions
stripeEvents: {
eventId: string (indexed, unique),
processedAt: number,
eventType: string,
}
// Idempotency table — prevents duplicate webhook processing
apiKeys: {
userId: Id<"users"> (indexed),
keyHash: string (indexed), // SHA-256 hash — NEVER store plaintext
prefix: string, // first 8 chars for UI identification
name: string,
scopes: string[], // ["read:market", "read:conflict", "*"]
tier: "starter" | "business",
expiresAt?: number,
lastUsedAt?: number,
createdAt: number,
revokedAt?: number,
}
// 256-bit random keys (crypto.getRandomValues), prefixed wm_live_ / wm_test_
// Constant-time comparison via crypto.timingSafeEqual on hash
usage: {
apiKeyId: Id<"apiKeys"> (indexed),
date: string, // YYYY-MM-DD
endpoint: string,
count: number,
}
savedViews: {
userId: Id<"users"> (indexed),
name: string,
panels: string[],
mapLayers: object,
watchlistSymbols: string[],
createdAt: number,
}
alertRules: {
userId: Id<"users"> (indexed),
name: string,
type: "threshold" | "event" | "keyword",
config: object,
channels: object[],
enabled: boolean,
createdAt: number,
}
auditLog: {
userId: Id<"users"> (indexed),
action: string,
resource: string,
metadata: object,
ip?: string,
createdAt: number,
}
// Structured audit for: auth events, key lifecycle, billing changes, entitlement decisions
Key file: convex/schema.ts
Acceptance criteria:
- All tables created with proper indexes
- Schema passes Convex validation (
npx convex dev) entitlementstable has unique constraint onuserId
Issue #2.2: User CRUD mutations & queries
Title: feat(backend): user CRUD mutations & queries
Labels: backend, convex, P0
Priority: P0 | Size: M | Dependencies: #2.1
Description: Implement Convex mutations and queries for user management.
Functions:
users.getByClerkId(clerkId)— queryusers.getByApiKey(keyHash)— query (joins apiKeys → users → entitlements)users.create({ clerkId, email, name })— mutation (from Clerk webhook)users.update({ userId, ...fields })— mutationusers.anonymize(userId)— mutation (for account deletion — tombstone PII, preserve audit/billing)entitlements.recompute(userId)— mutation (rebuild from active subscriptions)entitlements.getByUserId(userId)— queryauditLog.write({ userId, action, resource, metadata, ip })— mutation
Acceptance criteria:
- CRUD operations work via Convex dashboard
recomputecorrectly derives entitlements from multiple subscriptions- Anonymize replaces PII with
deleted-{hash}but preserves audit records - Automated: unit tests for entitlement recomputation (free, pro, api_starter, pro+api_business)
Issue #2.3: API key generation, hashing, and validation
Title: feat(backend): API key generation (wm_live_xxx), hashing, validation
Labels: backend, convex, P1
Priority: P1 | Size: M | Dependencies: #2.1
Description: Implement secure API key lifecycle management.
Implementation:
- Generation: 256-bit random via
crypto.getRandomValues(), prefixedwm_live_orwm_test_ - Storage: SHA-256 hash only in Convex. Plaintext returned once on creation — never again.
- Validation: constant-time comparison via
crypto.timingSafeEqualon hashed input - Scopes: per-key permission list (e.g.,
["read:market", "read:conflict", "*"]) - Expiry: optional
expiresAtfield - Rotation: create new key → user confirms → revoke old key
- Audit: all create/revoke/rotate events logged to
auditLog
Functions:
apiKeys.create({ userId, name, scopes, tier })— returns plaintext key ONCEapiKeys.validate(keyHash)— query, returns entitlements if validapiKeys.revoke(keyId)— mutation, setsrevokedAtapiKeys.listByUser(userId)— query (returns prefix + metadata, never hash)
Acceptance criteria:
- Key format:
wm_live_<32 hex chars> - Plaintext never stored or logged
- Revoked keys return 401
- Expired keys return 401
- Automated: hash/verify round-trip test, constant-time comparison test
Issue #2.4: Usage tracking — daily counters
Title: feat(backend): usage tracking — daily counters per API key per endpoint
Labels: backend, convex, P2
Priority: P2 | Size: S | Dependencies: #2.1
Description: Track API usage per key per day for billing and dashboard display.
Functions:
usage.record(apiKeyId, endpoint)— mutation (increment or create daily counter)usage.getDaily(apiKeyId, date)— queryusage.getMonthly(apiKeyId, month)— query (aggregate)
Acceptance criteria:
- Daily counters increment correctly
- Monthly aggregation sums daily values
Phase 3: Payments — Stripe (Weeks 3–4)
Issue #3.1: Stripe products and prices
Title: feat(payments): Stripe products — Pro Monthly/Annual + API Starter/Business
Labels: payments, infra, P0
Priority: P0 | Size: S | Dependencies: #0.2
Description: Create Stripe products and price objects for all tiers.
Products:
- World Monitor Pro Monthly — $X/mo
- World Monitor Pro Annual — $X/yr (discount)
- World Monitor API Starter — $Y/mo (1,000 req/day, 5 webhook rules)
- World Monitor API Business — $Z/mo (50,000 req/day, unlimited webhooks + SLA)
Environment variables:
STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WEBHOOK_SECRETSTRIPE_PRO_MONTHLY_PRICE_ID,STRIPE_PRO_ANNUAL_PRICE_IDSTRIPE_API_STARTER_PRICE_ID,STRIPE_API_BUSINESS_PRICE_ID
Acceptance criteria:
- Products created in Stripe test mode
- Price IDs stored as env vars
.env.exampleupdated
Issue #3.2: Checkout flow via Convex HTTP action
Title: feat(payments): checkout flow — Convex HTTP action → Stripe Checkout redirect
Labels: payments, backend, P0
Priority: P0 | Size: M | Dependencies: #2.1, #3.1
Description: Create a Convex HTTP action that generates a Stripe Checkout Session and returns the URL.
Implementation:
- Convex HTTP action receives authenticated user ID + product choice
- Look up or create Stripe customer (store
stripeCustomerIdon user) - Create Stripe Checkout Session with
success_urlandcancel_url - Return checkout URL for client-side redirect
- Handle upgrade (free → pro), API tier purchase, and plan switching
Acceptance criteria:
- Authenticated user can initiate checkout
- Redirects to Stripe Checkout
- Success URL leads back to dashboard with success message
- Stripe customer ID stored on user record
Issue #3.3: Stripe webhook handler
Title: feat(payments): Stripe webhook handler — subscription lifecycle in Convex
Labels: payments, backend, P0
Priority: P0 | Size: L | Dependencies: #3.2
Description: Handle Stripe webhook events to manage subscription lifecycle in Convex.
Safety requirements:
- Signature verification:
stripe.webhooks.constructEvent()withSTRIPE_WEBHOOK_SECRET - Idempotency: check
stripeEventstable byevent.idbefore processing; skip duplicates - Event age monitoring: log alerts for events older than 5 minutes (indicates outage/retry), but do NOT reject them — legitimate Stripe retries can arrive late
- Subscription reconciliation: do NOT use a forward-only state machine. Fetch current subscription object via
stripe.subscriptions.retrieve()and reconcilestatus,current_period_end, anditems. This correctly handlespast_due → active, resumed subscriptions, and plan switches. - Dead-letter: failed processing logged to
auditLogwith full event payload for manual retry - Entitlement recomputation: every subscription change triggers
recomputeEntitlements(userId)+ Redis cache invalidation
Webhook events:
checkout.session.completed→ create subscription, link to user, recompute entitlementsinvoice.paid→ renew subscription periodinvoice.payment_failed→ update status topast_due, send warning email via Resendcustomer.subscription.updated→ reconcile from Stripe object, recompute entitlementscustomer.subscription.deleted→ mark canceled, recompute entitlements (downgrade)
Acceptance criteria:
- All 5 webhook events handled correctly
- Duplicate events are idempotent (no double processing)
- Entitlements update within seconds of payment
- Failed webhooks logged for manual retry
- Automated: webhook contract tests via Stripe CLI
trigger
Issue #3.4: Pricing page
Title: feat(payments): pricing page — replace waitlist form with real plans + checkout
Labels: payments, frontend, P1
Priority: P1 | Size: M | Dependencies: #3.2
Description:
Replace the current waitlist form on /pro with a real pricing page that initiates checkout.
Implementation:
- Side-by-side comparison: Free vs Pro vs API vs Enterprise
- Monthly/annual toggle for Pro
- "Get Started" buttons → Clerk sign-in (if not authed) → Stripe Checkout
- "Coming Soon" section for Enterprise with "Contact Sales" CTA
- Integrate with existing i18n (23 languages)
Acceptance criteria:
- Pricing page shows all tiers with features
- Checkout flow works end-to-end
- Works in all 23 supported languages
Issue #3.5: Billing management via Stripe Customer Portal
Title: feat(payments): billing management via Stripe Customer Portal
Labels: payments, frontend, P1
Priority: P1 | Size: S | Dependencies: #3.3
Description: Add a link/button that redirects to Stripe Customer Portal for self-service billing management (update payment method, view invoices, cancel subscription, switch plans).
Acceptance criteria:
- Portal link accessible from
/account/billing - User can update payment method, view invoices, cancel
Issue #3.6: 14-day free trial for Pro
Title: feat(payments): 14-day free trial for Pro
Labels: payments, backend, P2
Priority: P2 | Size: S | Dependencies: #3.3
Description: Configure Stripe to offer a 14-day trial for Pro tier (no credit card required). Trial expiry → email reminder via Resend. Auto-downgrade on trial end via webhook.
Acceptance criteria:
- Trial activates without credit card
- Reminder email sent 3 days before trial ends
- Auto-downgrade on expiry triggers entitlement recomputation
Phase 4: Feature Gating (Week 5)
Issue #4.1: Server-side entitlement verification in gateway.ts
Title: feat(gating): server-side entitlement verification in gateway.ts
Labels: gating, backend, P0
Priority: P0 | Size: L | Dependencies: #2.2, #2.3
Description: Add entitlement-aware middleware to the server gateway so pro-only endpoints are enforced server-side.
Implementation:
- After
validateApiKey()(gateway.ts line 161), inject entitlement check - Look up user entitlements from Redis cache (
ent:{userId}orent:key:{keyHash}) - If cache miss, query Convex, populate cache with 60s TTL
- Active invalidation:
recomputeEntitlements()deletes Redis cache entry immediately via Upstash REST API - Fail-closed: if Redis AND Convex both unavailable, return 503 (never grant unauthorized access)
- Check endpoint against entitlement matrix
- Return
403 { error: "Upgrade required", requiredPlan: "pro", upgradeUrl: "/pro" }for gated endpoints
Entitlement matrix:
| Endpoint Category | free_anon | free_authed | pro | api_starter | api_business |
|---|---|---|---|---|---|
| Public data (seismology, news, weather) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Market quotes, crypto, commodities | ✓ | ✓ | ✓ | ✓ | ✓ |
| Equity research (financials, targets) | — | — | ✓ | — | ✓ |
| AI briefs, flash alerts | — | — | ✓ | — | — |
| Economy analytics (correlations) | — | — | ✓ | — | ✓ |
| Risk scoring, scenario analysis | — | — | ✓ | — | ✓ |
API key migration (dual-read rollout):
- Phase A: validate against BOTH static
WORLDMONITOR_VALID_KEYSAND new entitlements. Log comparison metrics. - Phase B: after 1 week with 0 mismatches, flip flag to new system only. Keep env var as emergency rollback.
- Phase C: remove static key validation code after 30 days.
Key files:
server/gateway.ts— main middleware injection pointapi/_api-key.js— extend validation logicserver/_shared/rate-limit.ts— rate limit by entitlement tier
Acceptance criteria:
- Free user gets 403 on equity research endpoint
- Pro user gets 200 on equity research endpoint
- API starter gets 200 on data endpoints, 403 on dashboard-only features
- Fail-closed: 503 when Redis + Convex both down
- Dual-read metrics dashboard shows match/mismatch counts
- Automated: E2E entitlement gating tests per tier
Issue #4.2: Client-side plan context service
Title: feat(gating): client-side plan context service (src/services/plan-context.ts)
Labels: gating, frontend, P0
Priority: P0 | Size: M | Dependencies: #1.2, #2.2
Description: Create a client-side service that exposes user plan/entitlements for UI gating.
Implementation:
- New service:
src/services/plan-context.ts - On auth, query user entitlements from Convex
- Expose helpers:
isPro(),hasApiAccess(),getPlan(),hasFeature(name) - Replace ALL
getSecretState('WORLDMONITOR_API_KEY').presentchecks with plan context - Works for both web (Clerk session) and desktop (Clerk + Tauri keychain)
- Include
computedAttimestamp for staleness detection
Key files to update:
src/components/Panel.ts— replacegetSecretStatechecksrc/components/DeckGLMap.ts— replace layer premium checksrc/components/GlobeMap.ts— replace layer premium checksrc/app/panel-layout.ts— replace_wmKeyPresentlogic
Acceptance criteria:
isPro()returns true for pro users, false for free- All
getSecretState('WORLDMONITOR_API_KEY')references replaced - Plan context updates within 60s of subscription change
Issue #4.3: Refactor panel/layer premium flags
Title: feat(gating): refactor panel/layer premium flags → plan context
Labels: gating, frontend, P1
Priority: P1 | Size: M | Dependencies: #4.2
Description:
Update panel and map layer configurations to use the new plan context instead of desktop-only isDesktopRuntime() checks.
Changes:
src/config/panels.ts— premium flags read from plan context, apply to web AND desktopsrc/config/map-layer-definitions.ts— same- Locked panel CTA: "Upgrade to Pro" → links to pricing page (not waitlist)
- Expand locked panels beyond current 2+4 to cover all pro-tier features
Acceptance criteria:
- Premium gating works on web (not just desktop)
- Locked panels link to
/propricing page - Enhanced panels show "PRO" badge for free users
Issue #4.4: Per-plan rate limiting
Title: feat(gating): per-plan rate limiting in gateway
Labels: gating, backend, P1
Priority: P1 | Size: M | Dependencies: #4.1
Description: Implement tiered rate limiting based on user plan.
Rate limits:
| Tier | Requests/day | Requests/min |
|---|---|---|
| Free (no auth) | 100 | 5 |
| Free (authenticated) | 500 | 10 |
| Pro | 10,000 | 60 |
| API Starter | 1,000 | 30 |
| API Business | 50,000 | 300 |
Unauthenticated identity:
- Key:
cf-connecting-ip+ endpoint path bucket - Trusted-proxy rule: only honor
cf-connecting-ipfrom Cloudflare IP ranges. Non-CF sources fall back to actual remote address. Log spoofing attempts. - Turnstile challenge at 50% daily quota
- Abuse flag at 3x daily limit
Acceptance criteria:
- Free user rate-limited at 100 req/day
- Pro user rate-limited at 10,000 req/day
- Rate limit headers returned:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset - Automated: rate limit integration tests per tier
Phase 5: User Dashboard (Weeks 6–7)
Issue #5.1: Account page
Title: feat(dashboard): /account page — profile, plan badge, API keys
Labels: dashboard, frontend, P1
Priority: P1 | Size: M | Dependencies: #1.2, #2.2, #2.3
Description:
Create an account page at /account showing profile info, current plan, and API key management.
Sections:
- Profile: name, email (from Clerk), avatar
- Plan: current plan badge, expiry date, upgrade button
- API Keys: list keys (prefix only), create, copy (once), revoke, regenerate
- Referral: referral code, count, share link (preserved from waitlist)
Acceptance criteria:
- Profile info displayed from Clerk
- Plan badge shows current tier
- API key CRUD works (create shows plaintext once, subsequent views show prefix only)
- Referral stats visible
Issue #5.2: Billing page
Title: feat(dashboard): /account/billing — invoices, plan management
Labels: dashboard, frontend, P1
Priority: P1 | Size: M | Dependencies: #3.3
Description: Create a billing page showing current subscription, next payment date, and link to Stripe Customer Portal.
Sections:
- Current plan + next billing date + amount
- Payment method (last 4 digits)
- Link to Stripe Customer Portal for self-service management
- Upgrade/downgrade buttons
Acceptance criteria:
- Shows current plan and billing cycle
- Stripe portal link works
- Upgrade/downgrade triggers new checkout
Issue #5.3: API usage stats
Title: feat(dashboard): API usage stats with daily/monthly charts
Labels: dashboard, frontend, P2
Priority: P2 | Size: M | Dependencies: #2.4, #5.1
Description: Display API usage charts on the account page (daily and monthly breakdowns).
Acceptance criteria:
- Daily usage bar chart
- Monthly aggregation
- Per-endpoint breakdown
Issue #5.4: Notification preferences & delivery channels
Title: feat(dashboard): notification preferences & delivery channels config
Labels: dashboard, frontend, P2
Priority: P2 | Size: M | Dependencies: #5.1
Description: Settings page for configuring notification delivery channels (Slack webhook URL, Telegram bot, Discord webhook, email preferences).
Acceptance criteria:
- Add/remove delivery channels
- Test notification button per channel
- Channel credentials encrypted at rest
Issue #5.5: API documentation page
Title: feat(docs): interactive API docs page with OpenAPI 3.1 from proto defs
Labels: dashboard, docs, P2
Priority: P2 | Size: L | Dependencies: #4.1
Description: Generate OpenAPI 3.1 spec from existing sebuf proto definitions (50+ RPCs across 15+ domains). Host interactive API explorer.
Implementation:
- Parse proto files with
(sebuf.http.config)annotations - Generate OpenAPI 3.1 spec
- Host via Swagger UI or Redoc at
/developersor/api/docs - Per-endpoint plan requirements documented
- Code examples in curl, Python, JS/TS
Acceptance criteria:
- OpenAPI spec covers all public endpoints
- Interactive explorer works
- Plan requirements shown per endpoint
Phase 6: Pro Features (Weeks 8–12)
Issue #6.1: Equity research data pipeline
Title: feat(pro): equity research — financials, analyst targets, valuation metrics
Labels: pro-feature, backend, P1
Priority: P1 | Size: XL | Dependencies: #4.1
Description: Build a new data pipeline for equity research features (pro-only).
Data to add:
- Financial statements (income, balance sheet, cash flow)
- Analyst consensus price targets
- Valuation metrics (PE, PB, EV/EBITDA)
- Earnings calendar
Sources (evaluate): Finnhub premium, Financial Modeling Prep (FMP), Alpha Vantage
Implementation:
- New sebuf proto service:
worldmonitor/equity/v1/ - RPCs:
get-company-financials,get-analyst-consensus,get-valuation-metrics,list-earnings-calendar - Redis caching via
cachedFetchJsonpattern - New panel: Equity Research dashboard (pro-only, gated via entitlements)
Acceptance criteria:
- Financial data available for major US stocks
- Analyst targets displayed with consensus rating
- Equity panel shows for pro users, locked for free
- Data refreshes at least daily
Issue #6.2: AI daily briefs engine
Title: feat(pro): AI daily briefs engine — scheduled LLM summaries
Labels: pro-feature, backend, P1
Priority: P1 | Size: XL | Dependencies: #4.1, #6.5
Description: Build a scheduled AI briefing system that synthesizes overnight developments and delivers via configured channels.
Implementation:
- Cron job (Railway or Convex cron) runs at configurable time per user timezone
- Aggregates latest data from Redis bootstrap keys (40+ keys exist)
- Ranks events by user's configured focus areas (markets, geopolitics, energy, etc.)
- Generates structured brief via Groq LLM (infrastructure exists in
deduct-situation.ts) - Stores brief in Convex
briefstable - Delivers via configured channels (email, Slack, Telegram, Discord)
Flash alerts: real-time event detection → LLM classification (existing classify-event RPC) → push notification
Acceptance criteria:
- Daily brief generated and delivered
- Focus areas configurable per user
- Brief stored and viewable in dashboard
- Flash alerts delivered within 5 minutes of event
Issue #6.3: Sub-60s data refresh
Title: feat(pro): sub-60s data refresh for pro users
Labels: pro-feature, backend, P1
Priority: P1 | Size: L | Dependencies: #4.1
Description: Reduce data refresh interval for pro users from 5-15 minutes to <60 seconds.
Phased approach:
- Phase 1: reduce client-side polling interval based on plan (simplest — just change
DataLoaderManagerinterval for pro users) - Phase 2: Server-Sent Events (SSE) for high-frequency data (markets, alerts) — push new data as it arrives
Acceptance criteria:
- Pro users see data refresh <60s
- Free users unchanged (5-15 min)
- Server load monitored (10x more requests from pro)
Issue #6.4: Server-side watchlists & custom views
Title: feat(pro): persistent server-side watchlists & custom views
Labels: pro-feature, fullstack, P1
Priority: P1 | Size: M | Dependencies: #2.1, #4.2
Description:
Migrate watchlists from localStorage to Convex savedViews table for cross-device sync.
Currently localStorage-only:
src/services/market-watchlist.tssrc/services/aviation/watchlist.ts
Implementation:
- On first sign-in, import existing localStorage watchlists to Convex
- Sync changes bidirectionally (Convex → client on load, client → Convex on change)
- Cross-device sync (web ↔ desktop)
Acceptance criteria:
- Watchlists persist across devices
- localStorage data migrated on first sign-in
- Offline-first: works without connection, syncs on reconnect
Issue #6.5: Delivery channels — Slack, Telegram, Discord, Email
Title: feat(pro): delivery channels — Slack, Telegram, Discord, Email
Labels: pro-feature, backend, P1
Priority: P1 | Size: XL | Dependencies: #2.1
Description: Build multi-channel delivery infrastructure for AI briefs and alerts.
Channels (in priority order):
- Email — Resend (already integrated). Extend for formatted briefs/alerts.
- Slack — incoming webhook URL (user provides). Format messages with blocks.
- Telegram — Bot API. Create
@WorldMonitorBot. User starts conversation, storechat_id. - Discord — webhook URL (user provides). Format with embeds.
- WhatsApp — P3 (requires Twilio/Meta business verification, highest cost)
Security:
- Webhook URL allowlisting: only
hooks.slack.com,discord.com/api/webhooks, Telegram API - Secrets encrypted via server-managed envelope encryption (
APP_ENCRYPTION_KEYenv var) - PII redacted from outbound payloads
- Per-channel signing/verification where supported
Acceptance criteria:
- Email delivery works (formatted brief)
- Slack webhook delivery works
- Telegram bot delivery works
- Discord webhook delivery works
- Secrets encrypted at rest
- Test notification button per channel
Issue #6.6: Economy analytics — correlation views
Title: feat(pro): economy analytics — GDP/inflation/rates correlation views
Labels: pro-feature, frontend, P2
Priority: P2 | Size: L | Dependencies: #4.1
Description: Build correlation views on top of existing economic data (FRED, BIS, World Bank RPCs already exist).
New visualizations:
- GDP growth vs market performance
- Inflation trends vs central bank rates
- Growth cycle detection and labeling
- Cross-country comparison charts
Acceptance criteria:
- Correlation charts display correctly
- Data from existing FRED/BIS/World Bank endpoints
- Pro-only (gated via entitlements)
Issue #6.7: Risk monitoring — convergence alerting & scenario analysis
Title: feat(pro): risk monitoring — convergence alerting & scenario analysis
Labels: pro-feature, fullstack, P2
Priority: P2 | Size: L | Dependencies: #4.1
Description: Enhance existing risk analytics with scenario analysis and convergence alerting.
Existing engines (enhance, don't rebuild):
src/services/geo-convergence.ts— convergence detectionsrc/services/focal-point-detector.ts— focal point detectionsrc/services/country-instability.ts— CII scoringsrc/services/signal-aggregator.ts— signal aggregation
New:
- Scenario analysis UI (what-if modeling)
- Convergence alerting (push when signals converge in a region)
- Risk trend visualization over time
Acceptance criteria:
- Scenario analysis UI works
- Convergence alerts delivered via configured channels
- Pro-only (gated via entitlements)
Issue #6.8: 22-services-1-key
Title: feat(pro): 22-services-1-key — replace BYOK with Pro key for all services
Labels: pro-feature, fullstack, P1
Priority: P1 | Size: M | Dependencies: #4.1
Description: Pro users should NOT need to configure individual API keys for Finnhub, FRED, ACLED, etc. A single World Monitor Pro subscription gives access to all 24 services.
Implementation:
- Server-side: pro requests use World Monitor's own upstream API keys (already configured as env vars)
- Free tier: continues using BYOK via desktop settings panel
- Gateway identifies pro user → skips BYOK requirement → uses server-side keys for upstream calls
Key files:
src/services/settings-constants.ts— 20+ key definitionsserver/gateway.ts— skip BYOK check for pro users
Acceptance criteria:
- Pro user sees data without configuring any individual API keys
- Free user still uses BYOK
- No upstream API key leakage to client
Phase 7: API Tier (Weeks 10–14, separate product)
Issue #7.1: API key issuance & management portal
Title: feat(api-tier): API key issuance & management portal
Labels: api-tier, fullstack, P1
Priority: P1 | Size: M | Dependencies: #2.3, #5.1
Description: Extend the account page with API key management specifically for API tier subscribers.
Features:
- Create multiple keys with different names/scopes
- View usage per key
- Rotate keys (create new → confirm → revoke old)
- Set per-key rate limits within tier allowance
Acceptance criteria:
- Multiple keys can be created
- Per-key usage visible
- Key rotation flow works
Issue #7.2: Per-key usage tracking with daily limits
Title: feat(api-tier): per-key usage tracking with daily limits
Labels: api-tier, backend, P1
Priority: P1 | Size: M | Dependencies: #2.4, #4.4
Description: Track and enforce daily usage limits per API key based on tier (Starter: 1,000/day, Business: 50,000/day).
Implementation:
- Increment
usagecounter on each request - Check daily total before processing
- Return
429withRetry-Afterheader when limit exceeded - Dashboard shows usage vs limit
Acceptance criteria:
- Daily limit enforced per key
- 429 returned with retry info when exceeded
- Usage dashboard shows consumption
Issue #7.3: OpenAPI 3.1 spec from sebuf protos
Title: feat(api-tier): OpenAPI 3.1 spec auto-generation from sebuf protos
Labels: api-tier, docs, P2
Priority: P2 | Size: L | Dependencies: None
Description: Auto-generate OpenAPI 3.1 specification from existing sebuf proto definitions.
Acceptance criteria:
- Spec covers all public RPC endpoints
- Plan requirements documented per endpoint
- Code examples in curl, Python, JS/TS
Issue #7.4: Webhook delivery system
Title: feat(api-tier): webhook delivery system with retry & HMAC signatures
Labels: api-tier, backend, P2
Priority: P2 | Size: L | Dependencies: #7.1
Description: Allow API tier subscribers to configure webhook endpoints for event delivery.
Implementation:
- Convex table:
webhookEndpoints(userId, url, events, secret) - HMAC-SHA256 signature on each delivery
- Exponential backoff retry (3 attempts)
- Starter: 5 webhook rules; Business: unlimited
- Delivery log with status codes
Acceptance criteria:
- Webhook delivery works with signature
- Failed deliveries retried with backoff
- Tier limits enforced
Phase 8: Enterprise (Months 4–12+, all P3)
Issue #8.1: Organization accounts & team management
Title: feat(enterprise): organization accounts & team management
Labels: enterprise, fullstack, P3
Priority: P3 | Size: XL | Dependencies: #1.1
Description: Multi-user organizations with shared dashboards, seat management, and invite flow.
Issue #8.2: RBAC
Title: feat(enterprise): RBAC (role-based access control)
Labels: enterprise, backend, P3
Priority: P3 | Size: L | Dependencies: #8.1
Description: Role-based access: admin, analyst, viewer. Per-role permissions for panels, data access, and configuration.
Issue #8.3: SSO (SAML/OIDC)
Title: feat(enterprise): SSO (SAML/OIDC via Clerk Enterprise)
Labels: enterprise, auth, P3
Priority: P3 | Size: L | Dependencies: #8.1
Description: Enterprise SSO via Clerk's Enterprise plan. Requires Clerk Enterprise subscription.
Issue #8.4: TV/SOC display mode
Title: feat(enterprise): TV/SOC display mode
Labels: enterprise, frontend, P3
Priority: P3 | Size: M | Dependencies: None
Description:
Full-screen dashboard for wall displays with auto-rotating panels and custom layouts. Some exists in src/services/tv-mode.ts.
Issue #8.5: White-label & embeddable panels
Title: feat(enterprise): white-label & embeddable panels
Labels: enterprise, frontend, P3
Priority: P3 | Size: XL
Description: Your brand, your domain, your desktop app. Embeddable iframe panels (50+ available).
Issue #8.6: Satellite imagery & SAR integration
Title: feat(enterprise): satellite imagery & SAR integration
Labels: enterprise, backend, P3
Priority: P3 | Size: XL
Description: Live-edge satellite imagery and SAR (Synthetic Aperture Radar) with change detection. Requires partnerships with Maxar/Planet.
Issue #8.7: AI agents with investor personas & MCP
Title: feat(enterprise): AI agents with investor personas & MCP
Labels: enterprise, backend, P3
Priority: P3 | Size: XL
Description: Autonomous intelligence agents using Model Context Protocol. Connect as tool to Claude, GPT, or custom LLMs.
Issue #8.8: 100+ data connectors
Title: feat(enterprise): 100+ data connectors
Labels: enterprise, backend, P3
Priority: P3 | Size: XL
Description: PostgreSQL, Snowflake, Splunk, Sentinel, Jira, Slack, Teams. Export to PDF, PowerPoint, CSV, GeoJSON. Multi-quarter effort.
Issue #8.9: On-premises / air-gapped deployment
Title: feat(enterprise): on-premises / air-gapped deployment
Labels: enterprise, infra, P3
Priority: P3 | Size: XL
Description: Docker-based on-premises deployment with air-gapped option. Full architecture rethink required — currently all cloud-native (Vercel + Convex + Railway).
Issue #8.10: Android TV app
Title: feat(enterprise): Android TV app
Labels: enterprise, frontend, P3
Priority: P3 | Size: XL
Description: Dedicated Android TV app for SOC walls and trading floors. Separate codebase.
Key Risks
- Vanilla TS + Clerk:
@clerk/clerk-jsheadless is less documented than React SDK. Prototype early. - Edge + Convex plan lookups: Vercel Edge can't import Convex. Must cache in Upstash Redis with active invalidation on webhook events.
- Sub-60s refresh at scale: 10x more requests from pro users. SSE/WebSocket needed long-term.
- API as separate product: Multiple Stripe subscriptions per user adds billing complexity.
entitlementsprojection table mitigates scattered logic. - Desktop auth + Tauri WKWebView: Known limitations. PKCE flow with
worldmonitor://deep link callback. - API key migration outage: Dual-read rollout (old + new in parallel) with comparison metrics before cutover.
Observability & Operations
- Audit logging: all auth events, key lifecycle, billing changes, entitlement decisions →
auditLogtable - Structured metrics: entitlement cache hit/miss ratio, webhook processing latency, API key validation latency
- Alerting: Slack/PagerDuty for webhook failures, entitlement errors, rate limit abuse spikes
- Incident rollback plan:
- Auth cutover: feature flag to disable Clerk and revert to static API key validation
- Payment cutover: Stripe test mode for staging; webhook replay via Stripe Dashboard
- Migration rollback:
registrationstable preserved 30 days alongsideusers
Data Retention & Privacy
- API usage data: retained 90 days, then aggregated to monthly summaries
- Audit logs: retained 1 year, then archived to cold storage
- AI-generated briefs: retained 30 days per user, older briefs auto-deleted
- PII handling: email + name stored in Convex (encrypted at rest). No PII in Redis cache. No PII in outbound delivery payloads.
- Account deletion: Clerk
user.deletedwebhook → delete user data. Audit logs and billing records are NOT deleted — user identifiers anonymized/tombstoned (deleted-{hash}). Stripe customer marked deleted; invoice history retained by Stripe.