SPEC 012 — Team Members and Roles
| Field | Value |
|---|---|
| Status | DRAFT |
| Priority | P1 — Core Product |
| Backend | equa-server/modules/organizations/, equa-server/modules/common/ |
| API | equa-server/modules/api/src/endpoints/organization-endpoints.ts |
| Frontend | equa-web/src/modules/team-members/, equa-web/src/modules/roles/ |
1. Feature Purpose
Team Members and Roles is the authorization backbone of the Equa platform. Every organization has members (people associated with the org), roles (named permission groups), and a permission system that gates access to cap table, documents, billing, and all other features. This module handles inviting new members, managing their profiles, creating custom roles, assigning permissions, and enforcing access control across every API endpoint.2. Current State (Verified)
2.1 Backend
| Component | Path |
|---|---|
| Persistence entities | equa-server/modules/persistence/src/schema.ts |
| Member/role reading | equa-server/modules/persistence/src/organizations/reading.ts |
| Member/role writing | equa-server/modules/persistence/src/organizations/writing.ts |
| Common reading | equa-server/modules/persistence/src/common/reading.ts |
| Common writing | equa-server/modules/persistence/src/common/writing.ts |
| Invitation logic | equa-server/modules/organizations/src/invitations.ts |
| Permission enforcement | equa-server/modules/api/src/site/authorization.ts |
| Permission enum | equa-server/modules/common/src/types.ts |
| API endpoints | equa-server/modules/api/src/endpoints/organization-endpoints.ts |
2.2 Frontend
| Component | Path |
|---|---|
| Team members module | equa-web/src/modules/team-members/ |
| Roles module | equa-web/src/modules/roles/ |
| Member form types | equa-web/src/modules/team-members/types.ts |
| Roles utility | equa-web/src/modules/roles/utility.ts |
| Roles service layer | equa-web/src/service/services/roles/ |
| Organizations service | equa-web/src/service/services/organizations/ |
| Shared permissions | equa-web/src/shared/components/permissions.tsx |
3. Data Model
Members
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | uuid | PK | Member identifier |
| title | varchar | nullable | Job title |
| citext | nullable | Case-insensitive email | |
| fullName | varchar | Display name | |
| dateOfBirth | date | nullable | |
| address | varchar | nullable | Postal address |
| phone | varchar | nullable | Phone number |
| organization | uuid | FK → Organizations | Owning organization |
| user | uuid | nullable, FK → Users | Linked Equa user account (null if not yet registered) |
| isIndividual | boolean | default: true | Individual vs entity/company member |
schema.ts lines 892–922
Roles
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | uuid | PK | Role identifier |
| name | varchar | Role display name | |
| description | varchar | default: ” | Role description |
| owner | uuid | nullable | Organization or user that owns the role |
| isShared | boolean | default: false | Whether role is shared across organizations |
schema.ts lines 1305–1321
MembersRoles (Join Table)
| Column | Type | Constraints |
|---|---|---|
| member | uuid | PK, FK → Members |
| role | uuid | PK, FK → Roles |
schema.ts lines 1296–1303
OrganizationsRoles (Join Table)
| Column | Type | Constraints |
|---|---|---|
| organization | uuid | PK, FK → Organizations |
| role | uuid | PK, FK → Roles |
schema.ts lines 1323–1330
Permissions
| Column | Type | Constraints |
|---|---|---|
| id | uuid | PK |
| name | varchar | Permission identifier string |
schema.ts lines 1332–1339
PermissionsRoles (Join Table)
| Column | Type | Constraints |
|---|---|---|
| permission | uuid | PK, FK → Permissions |
| role | uuid | PK, FK → Roles |
schema.ts lines 1341–1348
GlobalRolesUsers
| Column | Type | Constraints | Description |
|---|---|---|---|
| user | uuid | PK, FK → Users | |
| role | int | PK | Global role level (not UUID — uses integer enum) |
schema.ts lines 1353–1360
Invitations
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | uuid | PK | |
| citext | Invited email address | ||
| user | uuid | nullable, FK → Users | Set when invitee registers |
| organization | uuid | nullable, FK → Organizations | Target organization |
| status | InviteStatus | Current invitation state |
schema.ts lines 326–341
InviteStatus enum (common/src/types.ts lines 60–65):
invited = 1— Email sent, awaiting actionregistered = 2— User created an accountjoined = 3— User accepted and joined the organizationbounced = 4— Email delivery failed
MemberLimits
| Column | Type | Constraints | Description |
|---|---|---|---|
| organization | uuid | PK, FK → Organizations | |
| memberLimit | int | Maximum members allowed for this organization’s plan |
schema.ts lines 883–889
Relationships
4. API Endpoints
Member Endpoints
| Method | Path | Auth Guard | Description |
|---|---|---|---|
| GET | /organization/:organization/member | canViewMembers | List all members |
| GET | /organization/:organization/member/:member | canViewMember | Get single member |
| POST | /organization/:organization/member | canEditMembers | Create new member |
| PATCH | /organization/:organization/member/:member | canEditMembers | Update member |
| DELETE | /organization/:organization/member/:member | canEditMembers | Delete member |
| DELETE | /organization/:organization/user/:user | canEditMembers | Delete member by linked user |
| POST | /organization/:organization/member/invite | canEditMembers | Send invitations |
| GET | /organization/:organization/structure/member/limit | canViewOrganization | Get member limit |
| GET | /organization/:organization/user/:user | canViewOrganization | Get user’s permissions in org |
| GET | /entity/:entity/member | — | Get user’s member records across entities |
organization-endpoints.ts lines 132–236
Role Endpoints
| Method | Path | Auth Guard | Description |
|---|---|---|---|
| POST | /organization/:organization/role | canEditMembers | Create role |
| PUT | /organization/:organization/role/:role | canEditMembers | Update role |
| GET | /organization/:organization/role | canViewMembers | List roles |
| GET | /organization/:organization/role/:role | canViewMembers | Get single role |
| DELETE | /organization/:organization/role/:role | canEditMembers | Delete role |
| PUT | /organization/:organization/role/:role/member | canEditMembers | Assign members to role |
organization-endpoints.ts lines 274–304
Request/Response Schemas
5. Frontend Components
Team Members Pages
| Component | File | Purpose |
|---|---|---|
| MembersPage | team-members/pages/team-members.tsx | List all org members with invite/add actions |
| InviteMembersPage | team-members/pages/invite.tsx | Bulk invite members by email |
| NewMemberPage | team-members/pages/new-member-page.tsx | Create member form |
| EditMemberPage | team-members/pages/edit-member-page.tsx | Edit member form |
| MemberPage | team-members/components/team-members-profile/team-member.tsx | Member profile view |
Team Members Components
| Component | File | Purpose |
|---|---|---|
| MemberForm | team-members/components/member-form.tsx | Shared create/edit form (9 fields) |
| InvitedMemberRow | team-members/components/invited-member-row.tsx | Pending invitation display |
Roles Pages
| Component | File | Purpose |
|---|---|---|
| RolesPage | roles/pages/roles.tsx | List all roles |
| CreateRolePage | roles/pages/create-role-page.tsx | Create new role |
| ViewRolePage | roles/pages/view-role-page.tsx | Role detail view |
| EditRolePage | roles/pages/edit-role-page.tsx | Edit role |
| PermissionsPage | roles/pages/permissions-page.tsx | Organization permissions overview |
Roles Components
| Component | File | Purpose |
|---|---|---|
| RoleForm | roles/components/role-form.tsx | Shared create/edit role form |
| RolePermissionsTable | roles/components/role-permissions-table.tsx | Permission checkboxes for a role |
| PermissionsTable | roles/components/permissions-table.tsx | Full permissions matrix display |
Routes
| Route | Component |
|---|---|
/organization/:organization/members | MembersPage |
/organization/:organization/member/invite | InviteMembersPage |
/organization/:organization/member/new | NewMemberPage |
/organization/:organization/member/:member/edit | EditMemberPage |
/organization/:organization/member/:member | MemberPage |
/organization/:organization/roles | RolesPage |
/organization/:organization/role/new | CreateRolePage |
/organization/:organization/role/:role | ViewRolePage |
/organization/:organization/role/:role/edit | EditRolePage |
/organization/:organization/permissions | PermissionsPage |
Member Form Fields
6. Business Rules and Validation
6.1 Permissions System
Frontend permissions (15 named, defined inroles/utility.ts lines 35–126):
| Permission | Controls |
|---|---|
| deleteDocuments | Delete files from data room and governing documents |
| editBilling | Manage subscriptions, payment profiles |
| editCapTable | Issue shares, create security types, execute transfers |
| editDocuments | Upload, rename, move files |
| editIncentivePlan | Create/modify ESOP plans |
| editMembers | Add, edit, remove members and roles |
| editOrganizationDetails | Change org name, address, settings |
| signing | Sign certificates and agreements |
| viewGoverningDocuments | Read governing documents |
| viewCapTable | View shareholdings, security types, valuations |
| viewDocuments | Read data room files |
| viewIncentivePlan | View ESOP plans and grants |
| viewMembers | View member list and profiles |
| viewOrganization | View organization details |
| viewSelf | View own member profile |
common/src/permissions.ts, not exposed in frontend role UI):
fullVoting— Full voting rightspartialVoting— Partial voting rightswriteSite— Write access to organization sitereadSite— Read access to organization site
6.2 Permission Enforcement
Permission checks are implemented inapi/src/site/authorization.ts as guard functions:
canViewMember,canEditMembers,canViewMembers,canViewSomeMemberscanViewOrganization,canEditOrganizationcanViewCapTable,canEditCapTablecanViewDocuments,canEditDocuments,canDeleteDocuments- And others for billing, incentive plans, signing, governing docs
requires property. The guard checks the requesting user’s roles → permissions before allowing the request.
6.3 Invitation Flow
- Admin calls
POST /organization/:organization/member/invitewith member IDs - Server queries members by organization + IDs, deduplicates by email
- For each unique email: creates
Invitationrecord (status:invited), sends email viacommonNotificationTemplates.organizationInvitation - When invitee registers: status updates to
registered - When invitee accepts and joins: status updates to
joined - If email bounces: status updates to
bounced
organizations/src/invitations.ts lines 30–64
6.4 Member Limits
Organizations have amemberLimit set by their subscription plan. The GET /organization/:organization/structure/member/limit endpoint returns the current limit. The frontend displays a MemberLimitError component when the limit is reached.
Source: equa-web/src/shared/errors/member-limit-error.tsx
6.5 Role Assignment Rules
- Roles are scoped to an organization via
OrganizationsRoles - Members are assigned to roles via
MembersRoles(many-to-many) - A member can have multiple roles; permissions are the union of all assigned roles’ permissions
- Roles can be shared (
isShared = true) across organizations GlobalRolesUsersassigns platform-wide roles (admin) using integer role levels, separate from org-level RBAC
7. Acceptance Criteria
- Organization owner can create, edit, and delete custom roles with any combination of the 15 permissions
- Members can be assigned multiple roles; effective permissions are the union of all roles
- New members can be invited by email; invitation creates a record and sends an email notification
- Invitation status progresses through invited → registered → joined (or bounced)
- Member form captures all 9 editable fields (fullName, email, phone, title, dateOfBirth, address, user, roles, types)
- Member limit is enforced: adding members beyond the limit shows the MemberLimitError
- Permission guards on all 16 endpoints correctly deny access when the requesting user lacks the required permission
- Deleting a member removes their role assignments
- The 4 backend-only permissions (fullVoting, partialVoting, writeSite, readSite) do not appear in the frontend role editor
- Role deletion removes all PermissionsRoles and MembersRoles join records for that role
8. Risks and Edge Cases
| Risk | Impact | Mitigation |
|---|---|---|
| Email case sensitivity on invitations | Duplicate invitations to same address with different casing | email column uses citext (case-insensitive text) type |
| Orphaned role assignments after member deletion | Stale MembersRoles rows | Cascade delete or explicit cleanup on member removal |
| Member limit race condition | Two concurrent invites exceed limit | Server-side check in transaction before creating invitation |
| GlobalRolesUsers uses int, not UUID | Platform admin role check differs from org-level RBAC | Keep separate code paths; do not mix global and org role queries |
| Shared roles modified by one org affect another | Unintended permission changes | isShared roles should be read-only for non-owner orgs |
| Backend-only permissions not visible in UI | Users cannot understand or manage voting/site permissions | Document as internal/system permissions until frontend support is added |
9. Dependencies
| Dependency | Type | Status |
|---|---|---|
| 001-Authentication (user sessions) | Required before | DRAFT |
| 002-Organization Management | Required before | DRAFT |
| 011-Billing and Subscriptions (member limits) | Integrates with | DRAFT |