System Overview
Three Core Principles
Modular #-architecture — the system grows through bounded contexts. Current build = 25 active contexts (see Bounded Contexts). No cross-context
*.serviceimports — integration flows through the EventBus only (single whitelisted exception: theadminorchestration module).Federation-readiness — URI-based actor identifiers (
acct:[email protected], Webfinger-style), event-sourcing of critical domains. ActivityPub v1 is live: WebFinger, Actor, Outbox, Inbox with HTTP-signature signing (see thefederationcontext).Cost trajectory $30 → $500/month at 50 k MAU — one VPS (PM2 + nginx + Docker Postgres) + Expo + Cloudflare R2 + Centrifugo. No Convex / Firebase / Clerk / Supabase without an architectural ADR.
System Diagram
graph TB
subgraph Mobile["apps/mobile (Expo SDK 54 + RN 0.81, runtime 1.4.0)"]
MobileApp[React Native App]
end
subgraph Admin["apps/admin (Vite + React 19 + Ant Design SPA)"]
AdminPanel[Admin Panel]
end
subgraph API["apps/api (NestJS + Fastify) — 25 bounded contexts"]
direction TB
Identity[identity]
Interests[interests]
InviteTree[invite-tree]
Social[social]
Groups[groups]
Guilds[guilds]
Reputation[reputation]
Messaging[messaging]
AI[ai-assistant]
Notifications[notifications]
Posts[posts]
Media[media]
Discovery[discovery]
Geo[geo]
Economy[economy]
Contribution[contribution]
Wiki[wiki]
Events[events]
Deliberation[deliberation]
Education[education]
Projects[projects]
Videos[videos]
Audio[audio]
Federation[federation]
AdminMod[admin]
Shared[shared/ auth · access · embeddings · realtime · flags · health]
end
subgraph Data["Data Layer (same VPS)"]
PG[(Postgres 16 + pgvector, Docker :5433)]
R2[(Cloudflare R2)]
Centrifugo[Centrifugo WS]
end
subgraph Ext["External"]
Anthropic[Anthropic Claude Sonnet 4.6]
OpenAI[OpenAI Embeddings]
Fediverse[Fediverse / ActivityPub]
end
Mobile -->|REST + JWT| API
Admin -->|REST + JWT| API
API -->|Prisma| PG
API -->|Presigned URLs| R2
API -->|Publish| Centrifugo
Mobile -->|Subscribe| Centrifugo
AI -->|Vercel AI SDK| Anthropic
Shared -->|text-embedding| OpenAI
Federation <-->|signed HTTP| FediverseTech Stack
| Layer | Technology | Why |
|---|---|---|
| Mobile | Expo SDK 54 + RN 0.81 + New Architecture | One codebase, EAS OTA updates (runtimeVersion 1.4.0, expo-image disk cache) |
| Mobile UI | Tamagui v2.0.0-rc.0+ | Compiler-driven, design tokens first-class |
| Backend | NestJS + Fastify | Modules = bounded contexts |
| Database | Postgres 16 + pgvector (Docker on the VPS, port 5433) | Same version prod & dev, no Supabase |
| Auth | Custom JWT via NestJS + invite flow | No Supabase Auth |
| Real-time | Centrifugo (self-hosted, same VPS) | Scalable, federation-friendly |
| AI | Vercel AI SDK + Anthropic Claude Sonnet 4.6 | Streaming, provider-agnostic |
| Embeddings | OpenAI (pgvector storage) | Semantic feed ranking + search; graceful no-op without key |
| Storage | Cloudflare R2 | Zero egress cost for video |
| Web Admin | Vite + React 19 + Ant Design | Static SPA served by nginx — no Next.js / Vercel |
| Monorepo | Turborepo + pnpm | Simplicity, remote cache |
| State | TanStack Query + Zustand + MMKV | Standard, offline-first |
| Push | Expo Push | 600 notif/sec, zero infra |
| Observability | PostHog + Sentry | Free tiers cover Alpha |
| Hosting | One VPS regulus (PM2 + nginx + Docker Postgres) | API + admin + docs + Centrifugo + DB on one box; Cloudflare TLS; mobile via EAS |
Monorepo Structure
regulus/
├── apps/
│ ├── api/ NestJS modular monolith
│ │ ├── prisma/ Schema + migrations (75 migrations)
│ │ └── src/
│ │ ├── modules/ One folder = one bounded context (25)
│ │ └── shared/ Auth, access matrix, embeddings, realtime, flags, health
│ ├── mobile/ Expo SDK 54 (iOS + Android)
│ │ └── src/
│ │ ├── screens/ Full-screen views
│ │ ├── components/ Local UI components
│ │ └── lib/ API client, auth, storage
│ ├── admin/ Vite + React 19 + Ant Design SPA
│ │ └── src/
│ │ ├── pages/ One file = one admin resource
│ │ ├── components/ Admin-specific components
│ │ └── lib/ Queries, auth, API client
│ └── docs/ VitePress documentation (this site)
├── packages/
│ ├── domain/ Shared types, Zod schemas, domain events
│ ├── ui/ Shared React Native components
│ ├── design-tokens/ Tamagui theme tokens
│ └── ai-prompts/ Anthropic prompt templates
├── scripts/
│ └── ops/ VPS ops (nightly DB backup → R2, etc.)
└── docs/
├── adr/ Architecture Decision Records (ADR-001 … ADR-009)
├── architecture/ Bounded context map, audits
└── setup/ Human setup guidesCross-Cutting Concerns
Reputation, Moderation, Governance, and Economy must integrate through events, not direct service calls:
// WRONG — cross-context service import
@Injectable()
class ProfileService {
constructor(private readonly reputationService: ReputationService) {}
}
// CORRECT — event-driven integration
@Injectable()
class ProfileService {
constructor(private readonly eventBus: EventBus) {}
async updateProfile(userId: string, data: UpdateProfileDto) {
await this.eventBus.publish(new ProfileUpdatedEvent({ userId, changes: data }));
}
}
// Reputation module listens
@OnEvent('profile.updated')
async handleProfileUpdated(event: ProfileUpdatedEvent) { /* ... */ }Two documented exceptions to the strict boundary rules:
adminmodule — a legitimate orchestration layer; the ESLintno-restricted-importscross-context rule explicitly ignoresapps/api/src/modules/admin/**so it may call services from any context for admin-only operations.- FK on
User— ADR-009 (draft,docs/adr/ADR-009-user-fk-exception.md) legalises Postgres foreign keys targetingUser(id)from any context (actor reference, GDPR cascade delete). FKs between two non-identity contexts remain forbidden — UUID columns only.