Bounded Contexts
Each folder under apps/api/src/modules/<name>/ is a bounded context. Cross-context service imports are forbidden by ESLint rule no-cross-context-import. Integration happens through the NestJS EventEmitter2 event bus.
Context Map
graph LR
Identity -->|invite_code.used| Reputation
Identity -->|invite_code.used| InviteTree
Interests -->|user_selected| Reputation
Posts -->|post.created| Reputation
Posts -->|post.replied| Reputation
Posts -->|post.reacted| Reputation
Posts -->|post.bookmarked| Reputation
Posts -->|post.reposted| Reputation
Posts -->|emoji_pay.sent| Economy
Posts -->|post.created| Contribution
Economy -->|wallet.bootstrap| Identity
Reputation -->|reputation.changed| Notifications
Social -->|circle.invited| Notifications
Messaging -->|message.posted| NotificationsContexts Reference
identity
Aggregate roots: User, InviteCode, Session, Persona
Responsibilities:
- Invite-only registration:
reserve → registerflow with 30-minute TTL - Invite code generation: format
REG-XXXXXX, allocation formulaclamp(5 + rep/50 + age/14d, 5, 30), default batch 5 - JWT-based auth: login, refresh, logout
- Personas: max 5 per user, one default (
Main), accent color tokens - Account lifecycle: password reset, GDPR deletion, ban/unban
- Profile: username, bio, avatar, city, country
Events emitted:
identity.invite_code.used— when registration consumes a codeidentity.user_registered— new user created
Events consumed: none (bootstrap EmojiWallet is triggered by economy module on identity.user_registered)
interests
Aggregate roots: Interest (catalog), UserInterest
Responsibilities:
- 3-level catalog (L1 category → L2 sub-interest → L3 deep-interest)
- Catalog tree cached for 10 minutes in-process
- Per-user depth 1–5 (Curious / Reading / Practicing / Deep / Lifework)
- Self-rated expertise 1–5 (distinct from depth)
- Rollup reputation to parents using
RATIOS = [1.0, 0.5, 0.25, 0.12, 0.06]
Events emitted:
interests.user_selectedinterests.depth_changed
Events consumed: none
reputation
Aggregate roots: ReputationEvent (ledger), ReputationSnapshot, ReputationVote, ReputationAppeal
Responsibilities:
- Per-interest reputation only — no global aggregate exposed publicly
- Append-only ledger (
reputation_events); snapshots materialised inreputation_snapshots - Peer voting:
+1 / 0 / -1per(voter, target, interest)triple - Sybil dampening: votes from users with very low total rep are down-weighted
- Level labels: Curious (0–9) / Reader (10–29) / Practitioner (30–49) / Adept (50–69) / Master (70–89) / Lifework (90+)
- Appeal + redemption flow:
pending → accepted/rejected, optionalrestoreDelta
Events consumed:
interests.user_selected→ +5 per selected interest (one-shot bootstrap)identity.invite_code.used→ +3 to inviter in their top interestposts.created→ +3 to author in post's interestposts.replied→ +2 to replierposts.reacted(first unique per user) → +1 to post authorposts.bookmarked(first unique) → +1 to post authorposts.reposted(first unique) → +1 to post author
social
Aggregate roots: Circle, CircleMember, CircleInvitation, UserFollow, UserBlock, UserMute
Responsibilities:
- Circle CRUD (max 10 per user)
- Built-in circle kinds:
general | family | work | inner - Bidirectional consent: owner invites → invitee accepts/declines
- Bond levels:
inner-circle | acquaintance - Unidirectional follow graph
- Block / mute between users
Events emitted:
circle.invitedcircle.member_joined
posts
Aggregate roots: Post, PostReply, PostReaction, PostBookmark, PostInterest, PostCoAuthor, PostSeries, PostBoost
Responsibilities:
- All content formats:
note | essay | photo | video | quote | thread | repost - Visibility:
public | circles | inner - Content depth 0–5 (surface → thesis)
- Multi-interest tagging (up to 5 via
PostInterest) - Co-authorship: invite → accept/decline,
splitShare0–100 %, primary author keeps ≥ 10 % - Threaded replies with kind:
reply | reaction | criticism | contribution - Scheduled publish (
publishAt), soft delete, edit, pin (max 3) #journalismtag: gate rep ≥ 20 in any interest- Series: create, order, follow for notifications
- Materialised counters: reactions, replies, shares, bookmarks, views
Events emitted:
posts.createdposts.repliedposts.reactedposts.bookmarkedposts.reposted
ai-assistant
Aggregate roots: AssistantSession, AssistantTurn, AssistantSuggestion
Responsibilities:
- 5-question reflection flow; each session = 5 turns
- Anthropic Claude Sonnet 4.6 via Vercel AI SDK for question generation and interest mapping
- Scripted fallback when
ANTHROPIC_API_KEYis absent POST /ai/map-interests— LLM maps free-text to catalog interestsPOST /ai/apply-suggestions— writes accepted suggestions toUserInterestPOST /ai/enhance-post— AI content enhancement in composerAssistantSuggestion.confidence: 0–1,depth: 1–5,reason: max 280 chars
economy
Aggregate roots: EmojiWallet, EmojiPayment (append-only), PostBoost
Responsibilities:
- EmojiPay: send mana on a post or reply with an emotion
- 8 emotions:
love | awe | joy | curiosity | gratitude | insight | calm | courage - 6 tiers: 0=1, 1=3, 2=10, 3=25, 4=50, 5=100 mana
- Rep thresholds per tier (in post's primary interest): 0/0/10/20/35/50
- Wallet: 50 starter mana, 5/day regen, 50 regen cap
- Co-author mana split on accepted
PostCoAuthorrows - Boost: tier 1 = ×1.5 for 24 h (10 mana), tier 2 = ×2 for 48 h (25 mana)
- Nightly regen cron; boost expiry cron
- All financial rows are append-only. Reversal = new row with
reversed: true.
notifications
Aggregate roots: Notification, NotificationPreference, PushToken
Responsibilities:
- In-app notification inbox
- Expo Push Notifications (
ExponentPushToken[...]) - Per-kind toggles stored as JSON in
NotificationPreference.kinds - Per-channel toggles:
push | email | inApp - Notifications created by subscribing to events from other contexts
messaging
Aggregate roots: Conversation, ConversationMember, Message, MessageReaction
Responsibilities:
- DM and group conversations (up to 50 members)
- DM restricted to users who share at least one circle
- Per-conversation mute (
ConversationMember.mutedAt) - Message emoji reactions (grapheme validated server-side)
- Real-time delivery via Centrifugo
wiki
Aggregate roots: WikiEntry, WikiProposal, WikiVote
Responsibilities:
- Per-interest knowledge base (one canonical entry per interest)
- Propose → community vote → auto-approve at
netVotes ≥ 3 - Rep gate for proposing/voting:
WIKI_MIN_REP = 5in interest WikiVote.value=+1 | -1;WikiProposal.netVotesis materialised counter- Status:
pending | approved | rejected
contribution
Aggregate roots: ContributionEvent (append-only), ContributionSnapshot
Responsibilities:
- Tracks overall platform contribution per user
- Event kinds:
post_created | reply_created | reaction_given | bookmark_given | emoji_pay_sent - Invite-tree inheritance: 50 % to inviter / 25 % to grand-inviter / 10 % to great-grand-inviter
- Snapshot recomputed nightly; also updated incrementally
discovery
Aggregate roots: ConnectionIntent
Responsibilities:
- Intent-based discovery:
networking | friends | romantic | mentor | mentee | collab GET /discovery/candidates— users with matching intentGET /discovery/mentors— users with expertise gap ≥ N in a shared interestisFollowingwired to follow graphisActive: falseopts user out of discovery entirely
invite-tree
Aggregate roots: InviteNode
Responsibilities:
- Materialised path tree (dot-separated UUIDs) of "who invited whom"
- Subtree queries via
path LIKE 'root_uuid.%' - Ancestors / descendants / lineage endpoints
- Used by Contribution context for inheritance ratios
admin (cross-cutting)
No aggregate roots. Admin endpoints proxy to other contexts.
Responsibilities:
- Users: list, role change, ban/unban, GDPR delete
- Posts: list, hide/unhide
- Reports: review queue (open / reviewed / dismissed)
- Reputation: votes viewer, appeal resolution
- Economy: wallet overview, payment history
- Wiki: proposal management
- Feature flags: toggle
emoji-pay | wiki | personas-ui | journalism - Audit log: append-only log of all privileged mutations
- Broadcast notifications
shared (not a context)
Shared infrastructure used by all contexts:
| Path | Purpose |
|---|---|
shared/auth/ | JwtAuthGuard, JwtStrategy, AdminGuard |
shared/access/ | AccessMatrixService, @RequiresRep decorator |
shared/prisma/ | PrismaService (singleton) |
shared/events/ | Typed event definitions (@regulus/domain) |
shared/guards/ | ThrottlerGuard configuration |
shared/feature-flags/ | FeatureFlagService + @RequiresFlag guard |
shared/realtime/ | CentrifugoService |
shared/email/ | EmailService (Resend) |
shared/health/ | GET /health liveness probe |