Reputation Module
Location: apps/api/src/modules/reputation/
Aggregate roots: ReputationEvent (ledger), ReputationSnapshot, ReputationVote, ReputationAppeal
Core Principle
Reputation in Regulus is per-interest only. There is no global reputation score exposed in the public API. A user's standing is always relative to a specific domain of knowledge.
Ledger + Snapshot Architecture
reputation_events (append-only ledger)
↓ materialised by
reputation_snapshots (fast-read cache per user×interest)- Never UPDATE a
reputation_eventsrow. - Every
record()call writes a new event AND upserts the snapshot in a single Prisma transaction. - Snapshot is the single source of truth for reads.
Automatic Reputation Sources
| Trigger | Delta | Notes |
|---|---|---|
| User selects interest (onboarding) | +5 | One-shot per interest |
| Invite code consumed | +3 | To inviter, in inviter's top interest |
| Post created | +3 | To author, in post's interest |
| Reply posted | +2 | To replier, if post has an interest |
| Post reacted (first unique reactor) | +1 | To post author |
| Post bookmarked (first unique bookmarker) | +1 | To post author |
| Post reposted (first unique reposter) | +1 | To post author |
| Peer vote accepted | delta per vote | See peer voting section |
Reputation Levels
| Label | Range | Description |
|---|---|---|
| Curious | 0–9 | Just starting |
| Reader | 10–29 | Engaged learner |
| Practitioner | 30–49 | Active practitioner |
| Adept | 50–69 | Recognised expertise |
| Master | 70–89 | Deep domain mastery |
| Lifework | 90+ | Defining contribution |
export function levelFor(value: number): LevelLabel {
if (value >= 90) return 'Lifework';
if (value >= 70) return 'Master';
if (value >= 50) return 'Adept';
if (value >= 30) return 'Practitioner';
if (value >= 10) return 'Reader';
return 'Curious';
}Peer Voting
Any user can cast a +1 / 0 / -1 vote on another user's reputation within a specific interest. One vote per (voter, target, interest) triple.
value = 0means the voter explicitly withdrew their vote (row is kept for comment history)- The same triple re-votes by updating the existing row (not creating a new one)
- A
comment(max 280 chars) can accompany the vote
Sybil Dampening
Votes from users with very low total reputation carry less weight. The exact dampening formula is applied in reputation.service.ts when summing peer votes into the ledger delta.
Reputation Rollup
When reputation is recorded in an L3 interest, it automatically propagates to parent interests:
const ROLLUP_RATIOS = {
target: 1.0,
parent: 0.5, // L2 if target is L3
grandparent: 0.25, // L1 if target is L3
};This means a +10 delta in heidegger-being-and-time also produces +5 in existentialism and +2 in philosophy.
Appeal & Redemption Flow
Users who believe their reputation was unfairly reduced can file an appeal.
stateDiagram-v2
[*] --> pending : User files appeal
pending --> accepted : Admin approves
pending --> rejected : Admin rejects
accepted --> [*] : Optional restoreDelta applied
rejected --> [*]Appeal Constraints
- One pending appeal per
(user, interest)at a time (partial unique index in DB) restoreDeltais optional — admin can set a custom restoration amount or resolve without restoringadminNotemax 500 chars- Terminal states:
acceptedandrejectedcannot be changed
API
| Method | Path | Description |
|---|---|---|
| GET | /reputation/appeals | My appeals list |
| POST | /reputation/appeals | File new appeal |
| GET | /admin/reputation/appeals | All pending appeals (admin) |
| PATCH | /admin/reputation/appeals/:id | Resolve appeal (admin) |
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /reputation/:userId | All interest snapshots for user |
| GET | /reputation/:userId/:interestId | Single interest snapshot + level |
| POST | /reputation/vote | Cast peer vote |
| GET | /reputation/votes?targetUserId=&interestId= | View votes |
| GET | /reputation/appeals | My appeals |
| POST | /reputation/appeals | New appeal |
Admin
The admin reputation tab shows:
- Per-interest reputation breakdown for any user
- Peer vote history (who voted on whom, with comments)
- Appeal queue with resolve action (accept / reject + optional restore delta + admin note)