From b98f09305b5fbc4352b90ba845a20b27aa29eb84 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 17 Feb 2026 12:30:14 -0700 Subject: [PATCH] docs: add MODULE_SPEC.md with permission model and capabilities Co-Authored-By: Claude Opus 4.6 --- MODULE_SPEC.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 MODULE_SPEC.md diff --git a/MODULE_SPEC.md b/MODULE_SPEC.md new file mode 100644 index 0000000..2e36843 --- /dev/null +++ b/MODULE_SPEC.md @@ -0,0 +1,110 @@ +# rVote — Decision Engine + +**Module ID:** `rvote` +**Domain:** `rvote.online` +**Version:** 0.1.0 +**Framework:** Next.js 16 / React 19 / Prisma / PostgreSQL +**Status:** Active + +## Purpose + +Credit-weighted conviction voting engine for collaborative governance. Spaces can run ranked proposals with configurable parameters — daily credit allocation, promotion thresholds, voting periods — and binary final votes. Integrates with the r*.online ecosystem via EncryptID passkey authentication. + +## Data Model + +### Core Entities (Prisma) + +| Model | Key Fields | Relationships | +|-------|-----------|---------------| +| **User** | id, did (EncryptID DID), username, totalCredits | has many SpaceMember, Proposal, Vote, FinalVote | +| **Space** | slug, name, visibility, promotionsThreshold, votingPeriodHours, dailyCredits, maxCredits, startingCredits | has many SpaceMember, SpaceInvite | +| **SpaceMember** | userId, spaceSlug, role (ADMIN/MEMBER), credits, lastCreditUpdate | belongs to User, Space | +| **SpaceInvite** | spaceSlug, email, token, role, expiresAt, maxUses, useCount | belongs to Space | +| **Proposal** | id, title, description, spaceSlug, authorId, status, voteCount, finalYes/No/Abstain | belongs to Space, User; has many Vote, FinalVote | +| **Vote** | userId, proposalId, weight, credits, decayTime | ranking phase vote | +| **FinalVote** | userId, proposalId, vote (YES/NO/ABSTAIN) | binary phase vote | + +### Proposal Status Flow + +``` +RANKING → (crosses threshold) → VOTING → (period expires) → PASSED / FAILED + → ARCHIVED (manual) +``` + +## Permission Model + +### Space Integration + +- **SpaceVisibility:** All four levels supported (public, public_read, authenticated, members_only) +- **Default role for open spaces:** PARTICIPANT (can view proposals and vote) + +### Capabilities + +| Capability | Required SpaceRole | AuthLevel | Description | +|-----------|-------------------|-----------|-------------| +| `view_proposals` | VIEWER | BASIC | See proposal list and details | +| `create_proposal` | PARTICIPANT | STANDARD | Submit new proposals | +| `cast_vote` | PARTICIPANT | STANDARD | Vote on ranking and final phases | +| `moderate_proposals` | MODERATOR | STANDARD | Edit/archive others' proposals | +| `configure_voting` | ADMIN | ELEVATED | Change space voting parameters | + +### Module-Specific Overrides + +Currently uses `SpaceRole { ADMIN, MEMBER }` in Prisma. Migration path: +- `MEMBER` → `PARTICIPANT` +- Add `VIEWER` and `MODERATOR` to the Prisma enum +- Replace `requireSpaceMembership()` / `requireSpaceAdmin()` with `hasCapability()` from SDK + +### Current Auth Implementation + +- NextAuth 5 (beta) + EncryptID session +- `requireSpaceMembership(slug, session)` checks SpaceMember table +- `requireSpaceAdmin(slug, session)` checks SpaceMember.role === ADMIN + +## API Endpoints + +| Method | Path | Auth Required | Capability | Description | +|--------|------|---------------|------------|-------------| +| GET | /api/spaces | No | — | List public spaces | +| POST | /api/spaces | Yes | — | Create a new space | +| GET | /api/spaces/[slug] | Depends | view_proposals | Get space info | +| PUT | /api/spaces/[slug] | Yes | configure_voting | Update space settings | +| GET | /api/spaces/[slug]/members | Yes | view_proposals | List space members | +| POST | /api/spaces/[slug]/members | Yes | configure_voting | Add member | +| GET | /api/proposals | Depends | view_proposals | List proposals in space | +| POST | /api/proposals | Yes | create_proposal | Create proposal | +| POST | /api/proposals/[id]/vote | Yes | cast_vote | Submit ranking vote | +| POST | /api/proposals/[id]/final-vote | Yes | cast_vote | Submit final vote | +| GET/POST | /api/spaces/[slug]/invites | Yes | configure_voting | Manage invites | + +## Canvas Integration + +rVote can embed as a `demo-poll` shape in the rSpace canvas: +- Shape type: `demo-poll` +- Displays question + vote counts inline on canvas +- Click to expand into full voting interface +- Real-time vote count sync via Automerge + +## Cross-Module Dependencies + +| Module | Integration | +|--------|------------| +| **rSpace** | Embedded poll shapes on canvas | +| **rFunds** | Proposals can trigger funding flows when passed | +| **rNetwork** | Voter graph visualization | +| **canvas-website** | Tldraw integration via shape system | + +## Local-First / Offline Support + +- Currently server-authoritative (Prisma/PostgreSQL) +- No offline vote caching +- Future: CRDT proposal state for offline draft proposals, sync on reconnect + +## Migration Plan + +1. Add `VIEWER` and `MODERATOR` to Prisma `SpaceRole` enum (migration) +2. Rename existing `MEMBER` data to `PARTICIPANT` (data migration) +3. Import `SpaceRole` from `@encryptid/sdk` types for client-side use +4. Replace `requireSpaceMembership()` with `hasCapability(role, cap, RVOTE_PERMISSIONS)` +5. Replace `requireSpaceAdmin()` with `hasCapability(role, 'configure_voting', RVOTE_PERMISSIONS)` +6. Add `resolveSpaceRole()` call in API route middleware