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.

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
POST/reputation/voteCast peer vote
GET/reputation/votes?targetUserId=&interestId=View votes
GET/reputation/appealsMy appeals
POST/reputation/appealsNew 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)

Regulus — invite-only social-knowledge platform