Skip to content

Guilds Module

Location: apps/api/src/modules/guilds/ (treasury: apps/api/src/modules/economy/guild-treasury.service.ts)

Aggregate roots: Guild, GuildMember, GuildApplication, GuildProposal, GuildVote, GuildTreasury, GuildLedgerEntry (append-only)

Merit-gated, self-governing interest-domain communities (ADR-006). Reputation gating goes through the shared AccessMatrixService — never a direct import of the reputation module.


Founding & Membership

  • Founding is rep-gated: AccessMatrixService.canProposeGuild requires rep ≥ 50 (GUILD_FOUND_THRESHOLD) in the target interest domain.
  • One active guild per interest — the canonical steward; a second create for the same interest returns 409.
  • Roles: founder | council | member. Join policy is apply; applying requires rep ≥ guild.minReputation (default 20, ceiling 1000) in the guild's interest. The rep gate is re-checked on approval.
  • The founder cannot leave — archive instead (soft delete). Council can't expel council; nobody expels the founder.

Governance (Inc 2 + 3)

Any member can open a proposal. 6 kinds: admit_member, grant_council, revoke_council, amend_charter, moderation, expel_member (target-kinds require payload.targetId).

ParameterValue
Vote window72 h (closesAt), lazily expired on read/vote
Quorum50 % of memberCount — by distinct voter count
Threshold60 % — by rep-weighted for / (for + against)
Vote weightmax(1, log₂(rep + 1)) from ReputationSnapshot in the guild's interest
Vote valuesfor | against | abstain (abstain counts for quorum, not threshold)

Votes are upserted (changeable while open); weight is re-evaluated on every change. Tallies are rebuilt from authoritative DB state inside a transaction. When a proposal passes, its effect executes in the same transaction (membership/role/charter change) and status moves passed → executed. Weight examples: rep 0 → 1.0, rep 10 → ~3.5, rep 100 → ~6.7.


Treasury (Wave 5)

Pooled mana per guild, living in the economy context (same EmojiWallet mana). GuildLedgerEntry is append-only — every movement is a new contribution/disbursement row, never an UPDATE.

  • contribute — any member, 1–10 000 mana; locks the contributor's wallet row (SELECT ... FOR UPDATE) before the balance check.
  • disburse — founder only, to a member; locks the treasury row FOR UPDATE so two concurrent disbursements can't both pass the balance check and overdraw.
  • distributeByContribution — founder splits totalMana across members weighted by lifetime contribution share (from the ledger), largest-remainder allocation so splits sum exactly; same treasury FOR UPDATE lock. Members who never contributed get nothing.

HTTP Endpoints

All under /api/v1, JwtAuthGuard.

MethodPathDescription
POST/guildsFound a guild (rep ≥ 50 in domain)
GET/guilds?interestId=&q=&limit=Discover guilds
GET/guilds/mineGuilds the viewer belongs to
GET/guilds/:idDetail + viewer relationship + canApply
PATCH/guilds/:idFounder/council edits metadata
DELETE/guilds/:idFounder archives
POST/guilds/:id/applyApply to join (rep-gated)
DELETE/guilds/:id/applyCancel my pending application
POST/guilds/applications/:applicationId/respondApprove/reject (manager)
GET/guilds/:id/applicationsPending applications (manager)
POST/guilds/:id/leaveLeave the guild
DELETE/guilds/:id/members/:memberIdExpel (manager)
PATCH/guilds/:id/members/:memberId/rolePromote/demote council ⇄ member (founder)
POST/guilds/:id/proposalsOpen a governance proposal (member)
GET/guilds/:id/proposals?status=List proposals (member)
GET/guilds/proposals/:proposalIdProposal detail + my vote (member)
POST/guilds/proposals/:proposalId/voteCast or change a vote (member)
GET/treasury/:guildIdTreasury balance + last 30 ledger entries
POST/treasury/:guildId/contributeMember contributes mana
POST/treasury/:guildId/disburseFounder pays a member
POST/treasury/:guildId/distributeContribution-weighted distribution

Events Emitted

EventPayload
guilds.guild.founded{ guildId, founderId }
guilds.member.joined{ guildId, memberId }
guilds.application.submitted{ guildId, userId, applicationId }
guilds.application.approved{ guildId, userId }
guilds.proposal.opened{ guildId, proposalId, proposerId }
guilds.proposal.executed{ proposalId, guildId, kind }

Admin

/admin/guilds (list, archive/restore), /admin/guilds/:id detail, expel member, resolve applications, force-close proposals — all audit-logged.

See also: /modules/economy (mana/wallets), /modules/reputation, /modules/access.

Regulus — invite-only social-knowledge platform