Skip to content

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_events row.
  • 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.
  • ReputationSnapshot carries a covering index (interestId, value) (migration 20260608000000) so the per-interest leaderboard filters by interest and sorts by value without a full scan + sort.

Automatic Reputation Sources ​

TriggerDeltaNotes
User selects interest (onboarding)+5One-shot per interest
Invite code consumed+3To inviter, in inviter's top interest
Post created+3To author, in post's interest
Reply posted+2To replier, if post has an interest
Post reacted (first unique reactor)+1To post author
Post bookmarked (first unique bookmarker)+1To post author
Post reposted (first unique reposter)+1To post author
Peer vote accepteddelta per voteSee peer voting section

Reputation Levels ​

LabelRangeDescription
Curious0–9Just starting
Reader10–29Engaged learner
Practitioner30–49Active practitioner
Adept50–69Recognised expertise
Master70–89Deep domain mastery
Lifework90+Defining contribution
typescript
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 = 0 means 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:

typescript
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.

mermaid
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)
  • restoreDelta is optional — admin can set a custom restoration amount or resolve without restoring
  • adminNote max 500 chars
  • Terminal states: accepted and rejected cannot be changed

API ​

MethodPathDescription
GET/reputation/appealsMy appeals list
POST/reputation/appealsFile new appeal
GET/admin/reputation/appealsAll pending appeals (admin)
PATCH/admin/reputation/appeals/:idResolve appeal (admin)

API Endpoints ​

MethodPathDescription
GET/reputation/:userIdAll interest snapshots for user
GET/reputation/:userId/:interestIdSingle interest snapshot + level
GET/reputation/leaderboard?interest=&limit=Top members in an interest
POST/reputation/voteCast peer vote
GET/reputation/votes?targetUserId=&interestId=View votes
GET/reputation/appealsMy appeals
POST/reputation/appealsNew appeal

Auth: every reputation route — including /leaderboard — is now @UseGuards(JwtAuthGuard). The leaderboard previously leaked member names + UUIDs to anonymous callers, which is unacceptable on an invite-only platform.


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)

Regulus — invite-only social-knowledge platform