> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Team Members and Roles

> Member management, role-based access control, permissions, invitations, and member limits

# 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                                             |
| email        | 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                   |

Source: `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 |

Source: `schema.ts` lines 1305–1321

### MembersRoles (Join Table)

| Column | Type | Constraints      |
| ------ | ---- | ---------------- |
| member | uuid | PK, FK → Members |
| role   | uuid | PK, FK → Roles   |

Source: `schema.ts` lines 1296–1303

### OrganizationsRoles (Join Table)

| Column       | Type | Constraints            |
| ------------ | ---- | ---------------------- |
| organization | uuid | PK, FK → Organizations |
| role         | uuid | PK, FK → Roles         |

Source: `schema.ts` lines 1323–1330

### Permissions

| Column | Type    | Constraints                  |
| ------ | ------- | ---------------------------- |
| id     | uuid    | PK                           |
| name   | varchar | Permission identifier string |

Source: `schema.ts` lines 1332–1339

### PermissionsRoles (Join Table)

| Column     | Type | Constraints          |
| ---------- | ---- | -------------------- |
| permission | uuid | PK, FK → Permissions |
| role       | uuid | PK, FK → Roles       |

Source: `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) |

Source: `schema.ts` lines 1353–1360

### Invitations

| Column       | Type         | Constraints                  | Description                |
| ------------ | ------------ | ---------------------------- | -------------------------- |
| id           | uuid         | PK                           |                            |
| email        | 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   |

Source: `schema.ts` lines 326–341

**InviteStatus enum** (`common/src/types.ts` lines 60–65):

* `invited = 1` — Email sent, awaiting action
* `registered = 2` — User created an account
* `joined = 3` — User accepted and joined the organization
* `bounced = 4` — Email delivery failed

### MemberLimits

| Column       | Type | Constraints            | Description                                          |
| ------------ | ---- | ---------------------- | ---------------------------------------------------- |
| organization | uuid | PK, FK → Organizations |                                                      |
| memberLimit  | int  |                        | Maximum members allowed for this organization's plan |

Source: `schema.ts` lines 883–889

### Relationships

```mermaid theme={null}
erDiagram
    Organizations ||--o{ Members : "has many"
    Organizations ||--o{ OrganizationsRoles : "has many"
    Organizations ||--o{ Invitations : "has many"
    Organizations ||--|| MemberLimits : "has one"
    Members ||--o{ MembersRoles : "assigned"
    Members }o--o| Users : "linked to"
    Roles ||--o{ MembersRoles : "assigned to"
    Roles ||--o{ OrganizationsRoles : "scoped to"
    Roles ||--o{ PermissionsRoles : "grants"
    Permissions ||--o{ PermissionsRoles : "granted by"
    Users ||--o{ GlobalRolesUsers : "has global"
    Users ||--o{ Invitations : "invited as"
```

***

## 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 |

Source: `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 |

Source: `organization-endpoints.ts` lines 274–304

### Request/Response Schemas

```typescript theme={null}
// POST /organization/:organization/role
interface NewRoleRequest {
  organization: Uuid;
  role: string;        // role name
  description: string;
  permission: Uuid[];  // array of permission IDs
}

// PUT /organization/:organization/role/:role
interface UpdateRoleRequest {
  organization: Uuid;
  role: Uuid;
  newRole: string;      // updated role name
  description: string;
  permission: Uuid[];
}

// PUT /organization/:organization/role/:role/member
interface NewMemberRoleRequest {
  organization: Uuid;
  role: Uuid;
  member: Uuid[];  // member IDs to assign
}

// GET /organization/:organization/role/:role → response
interface Role {
  id: Uuid;
  name: string;
  description: string;
  permissions: Uuid[];
  isShared: boolean;
}
```

***

## 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

```typescript theme={null}
interface CommonEditableMemberFields {
  title: string;
  email: string;
  roles: Uuid[];
  dateOfBirth: Date;
  address: Address;
  types: 'individual' | 'organization';
}

interface EditableMemberFields extends CommonEditableMemberFields {
  fullName: string;
  phone: string;
  user: Uuid;
}
```

***

## 6. Business Rules and Validation

### 6.1 Permissions System

**Frontend permissions** (15 named, defined in `roles/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                                |

**Backend-only permissions** (4 additional, in `common/src/permissions.ts`, not exposed in frontend role UI):

* `fullVoting` — Full voting rights
* `partialVoting` — Partial voting rights
* `writeSite` — Write access to organization site
* `readSite` — Read access to organization site

### 6.2 Permission Enforcement

Permission checks are implemented in `api/src/site/authorization.ts` as guard functions:

* `canViewMember`, `canEditMembers`, `canViewMembers`, `canViewSomeMembers`
* `canViewOrganization`, `canEditOrganization`
* `canViewCapTable`, `canEditCapTable`
* `canViewDocuments`, `canEditDocuments`, `canDeleteDocuments`
* And others for billing, incentive plans, signing, governing docs

Each endpoint declares its guard via the `requires` property. The guard checks the requesting user's roles → permissions before allowing the request.

### 6.3 Invitation Flow

1. Admin calls `POST /organization/:organization/member/invite` with member IDs
2. Server queries members by organization + IDs, deduplicates by email
3. For each unique email: creates `Invitation` record (status: `invited`), sends email via `commonNotificationTemplates.organizationInvitation`
4. When invitee registers: status updates to `registered`
5. When invitee accepts and joins: status updates to `joined`
6. If email bounces: status updates to `bounced`

Source: `organizations/src/invitations.ts` lines 30–64

### 6.4 Member Limits

Organizations have a `memberLimit` 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
* `GlobalRolesUsers` assigns 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  |
