SPEC 004: ESOP
Status: DRAFT Priority: P0 Created: 2026-02-21 Approved: pending Repo(s): equa-web, equa-server
1. Feature Purpose
The ESOP module manages employee stock option plans, equity incentive pools, and vesting schedules. It allows organizations to create board-approved equity incentive plans, allocate option pools tied to specific security types, grant options to individual team members with customizable vesting, and track exercise events. This is critical for startups and growth-stage companies that use equity compensation to attract and retain talent.2. Current State (Verified)
Frontend module:esop server module. Plan, pool, option, and vesting schedule entities live alongside other cap-table entities in the persistence layer.
3. Data Model
Entities
| Entity | Description | File |
|---|---|---|
| Plans | Board-approved equity incentive plans | equa-server/modules/persistence/src/entity/Plans.ts |
| PlansSecurities | Join table: plans ↔ securities | equa-server/modules/persistence/src/entity/PlansSecurities.ts |
| PlansVestingSchedules | Join table: plans ↔ vesting schedules | equa-server/modules/persistence/src/entity/PlansVestingSchedules.ts |
| Pools | Option pools allocated from a plan | equa-server/modules/persistence/src/entity/Pools.ts |
| Options | Individual option grants to members | equa-server/modules/persistence/src/entity/Options.ts |
| ContinuousVestingSchedules | Time-based vesting with cliff and frequency | equa-server/modules/persistence/src/entity/ContinuousVestingSchedules.ts |
| DiscreteVestingSchedules | Milestone / custom-date vesting | equa-server/modules/persistence/src/entity/DiscreteVestingSchedules.ts |
| VestingEvents | Individual vesting events within a discrete schedule | equa-server/modules/persistence/src/entity/VestingEvents.ts |
| TasksExerciseOption | Exercise-option workflow tasks | equa-server/modules/persistence/src/entity/TasksExerciseOption.ts |
Key Fields — Plans
| Field | Type | Nullable | Description |
|---|---|---|---|
hash | Hash | No | Primary key (content-addressed) |
organization | uuid | No | Owning organization |
name | string | No | Plan display name |
boardApprovalDate | date | No | Date the board approved this plan |
termYears | smallint | No | Plan duration in years |
approvedEquities | jsonb | Yes | Approved equity types and amounts |
createdBy | uuid | No | User who created the plan |
format | smallint | No | Plan format enum |
boardApprovalDocument | Hash | Yes | Reference to board resolution document |
incentivePlanDocument | Hash | Yes | Reference to the plan document itself |
Key Fields — Pools
| Field | Type | Nullable | Description |
|---|---|---|---|
organization | uuid | No | Owning organization |
security | Hash | No | Security type for this pool |
shares | numeric | No | Total shares allocated to pool |
pricePerShare | numeric | No | Strike / exercise price |
vestingSchedule | Hash | Yes | Default vesting schedule |
name | string | No | Pool display name |
boardApprovalDate | date | No | Board approval date |
options | Hash | Yes | Reference to associated options |
Key Fields — Options
| Field | Type | Nullable | Description |
|---|---|---|---|
organization | uuid | No | Owning organization |
security | Hash | No | Security type |
member | uuid | No | Grantee (team member) |
shares | numeric | No | Number of options granted |
startDate | date | No | Vesting start date |
vestingSchedule | Hash | Yes | Vesting schedule for this grant |
legend | Hash | Yes | Legend / restriction text |
note | text | Yes | Free-text note |
Key Fields — ContinuousVestingSchedules
| Field | Type | Nullable | Description |
|---|---|---|---|
durationMonths | smallint | No | Total vesting duration |
frequencyMonths | smallint | No | Vesting frequency (e.g. 1 = monthly) |
cliffMonths | smallint | No | Cliff period in months |
cliffAmount | numeric | No | Amount that vests at cliff |
cliffAmountType | smallint | No | Absolute shares or percentage |
entity | uuid | No | Owning entity (organization) |
name | string | No | Schedule display name |
vestsOn | smallint | No | Day-of-month vesting occurs |
Key Fields — DiscreteVestingSchedules
| Field | Type | Nullable | Description |
|---|---|---|---|
events | Hash | No | Reference to ordered vesting events |
entity | uuid | No | Owning entity |
isValueAbsolute | boolean | No | Whether event values are absolute shares or percentages |
name | string | No | Schedule display name |
Key Fields — VestingEvents
| Field | Type | Nullable | Description |
|---|---|---|---|
day | smallint | No | Day of event |
month | smallint | No | Month of event |
year | smallint | No | Year of event |
value | numeric | No | Shares or percentage that vest |
Key Fields — TasksExerciseOption
| Field | Type | Nullable | Description |
|---|---|---|---|
id | uuid | No | Primary key |
option | Hash | No | Option being exercised |
shares | numeric | No | Number of shares to exercise |
Relationships
4. API Endpoints
Served by the captable module endpoints.| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/organizations/:orgId/plans | Session | List all equity incentive plans |
| POST | /v1/organizations/:orgId/plans | Session | Create a new plan |
| GET | /v1/organizations/:orgId/plans/:hash | Session | Get plan details |
| PUT | /v1/organizations/:orgId/plans/:hash | Session | Update a plan |
| DELETE | /v1/organizations/:orgId/plans/:hash | Session | Delete a plan |
| GET | /v1/organizations/:orgId/pools | Session | List option pools |
| POST | /v1/organizations/:orgId/pools | Session | Create an option pool |
| GET | /v1/organizations/:orgId/pools/:hash | Session | Get pool details |
| PUT | /v1/organizations/:orgId/pools/:hash | Session | Update a pool |
| GET | /v1/organizations/:orgId/options | Session | List option grants |
| POST | /v1/organizations/:orgId/options | Session | Grant options to a member |
| GET | /v1/organizations/:orgId/options/:hash | Session | Get option grant details |
| PUT | /v1/organizations/:orgId/options/:hash | Session | Update an option grant |
| GET | /v1/organizations/:orgId/vesting-schedules | Session | List vesting schedules |
| POST | /v1/organizations/:orgId/vesting-schedules | Session | Create a vesting schedule |
| POST | /v1/organizations/:orgId/options/:hash/exercise | Session | Exercise options |
5. Frontend Components
| Component | File | Purpose |
|---|---|---|
| PlansPage | equa-web/src/modules/esop/components/PlansPage.tsx | List and manage equity incentive plans |
| PlanDetail | equa-web/src/modules/esop/components/PlanDetail.tsx | View/edit a single plan |
| PoolsPage | equa-web/src/modules/esop/components/PoolsPage.tsx | List and manage option pools |
| PoolDetail | equa-web/src/modules/esop/components/PoolDetail.tsx | View/edit a single pool |
| OptionsPage | equa-web/src/modules/esop/components/OptionsPage.tsx | List option grants |
| OptionGrantForm | equa-web/src/modules/esop/components/OptionGrantForm.tsx | Grant options to a member |
| VestingScheduleForm | equa-web/src/modules/esop/components/VestingScheduleForm.tsx | Create/edit vesting schedules |
| VestingTimeline | equa-web/src/modules/esop/components/VestingTimeline.tsx | Visual vesting timeline |
Routes
| Route | Component | Description |
|---|---|---|
/organizations/:orgId/esop/plans | PlansPage | Plan listing |
/organizations/:orgId/esop/plans/:hash | PlanDetail | Single plan detail |
/organizations/:orgId/esop/pools | PoolsPage | Pool listing |
/organizations/:orgId/esop/pools/:hash | PoolDetail | Single pool detail |
/organizations/:orgId/esop/options | OptionsPage | Option grants listing |
State Management
ESOP state is managed within theesop module store, holding loaded plans, pools, options, and vesting schedules. Data is fetched via captable API endpoints and cached locally.
6. Business Rules and Validation
| Rule | Description | Enforcement |
|---|---|---|
| ESOP-001 | A plan must have a board approval date before options can be granted under it | Backend |
| ESOP-002 | Pool shares cannot exceed the plan’s approved equity amount for the associated security type | Backend |
| ESOP-003 | Option grant shares cannot exceed the remaining unallocated shares in the pool | Backend |
| ESOP-004 | Cliff amount must not exceed total option grant shares | Backend |
| ESOP-005 | Vesting start date must be on or after the plan’s board approval date | Backend |
| ESOP-006 | Exercise shares cannot exceed the number of vested (but unexercised) shares at the time of exercise | Backend |
| ESOP-007 | termYears must be a positive integer (typically 10 years) | Both |
| ESOP-008 | Content-addressed Hash fields are immutable once created; updates produce new hashes | Backend |
6a. Vesting Calculation Logic (from Confluence KnowledgeBase)
Source: Confluence KnowledgeBase — Calculating Vesting (by Christopher Johnson)Vesting Schedules are functions that output a list of Vesting Events. A Vesting Event has two properties:
date (the vesting date) and value (the amount vested). Schedules can have relative dates (requiring a startDate parameter) and relative values (requiring a totalValue parameter).
Continuous Vesting Schedules
Continuous schedules are equations that procedurally generate Vesting Events. They are fully relative, requiring bothstartDate and totalValue.
| Property | Description |
|---|---|
frequency | Number of months between Vesting Events |
eventQuantity | Total number of Vesting Events (cliffs can reduce this) |
vestsOnDay | Day of the month, with three modes: Anniversary (day of startDate), First Day of Month, Last Day of Month |
cliffValue, the first vesting event equals the increment of each subsequent event.
Worked example: 1,000 options, 4-year vest, monthly frequency, 1-year cliff:
totalValue= 1,000,frequency= 1 (monthly),eventQuantity= 48- Cliff duration = 12 months → first 12 events suppressed
- At cliff (month 12): 250 options vest (12/48 * 1,000)
- After cliff: ~20.83 options vest per month (1,000 - 250) / 36
Cliffs
- A cliff is a duration of months before vesting begins
- Prevents events that would vest before the cliff ends
- Reduces the quantity of Vesting Events but does NOT alter the total schedule duration
- Does NOT alter the cadence of events after the cliff
- A cliff of 1 month negates the first event
- Optional
cliffValueproperty overrides the value of the first post-cliff event, altering the vesting curve steepness cliffValuecan be relative (percentage) or absolute (fixed number)
Discrete Vesting Schedules
Discrete schedules contain a user-defined list of Vesting Events. Events can have relative or absolute dates and values. The frontend UI only supports fully relative or fully absolute schedules (no mixing). Relative Discrete Vesting Event properties:month— 1-based offset from startDate.month. Resolved month:startDate.month + event.month - 1day— Either an integer orlastDayOfMonth. Unlike month, day is absolute (not offset from startDate.day)value— The amount vested at this event
7. Acceptance Criteria
- AC-1: Admin can create an equity incentive plan with board approval date, term, and approved equities
- AC-2: Admin can attach security types and vesting schedules to a plan
- AC-3: Admin can create an option pool under a plan with shares, price per share, and default vesting
- AC-4: Admin can grant options to a team member specifying shares, start date, and vesting schedule
- AC-5: System prevents granting more options than remaining pool capacity
- AC-6: Continuous vesting schedule correctly computes cliff and periodic vesting amounts
- AC-7: Discrete vesting schedule correctly applies per-event vesting on specified dates
- AC-8: Team member can exercise vested options via TasksExerciseOption workflow
- AC-9: Vesting timeline visualization accurately reflects grant schedule and cliff
- AC-10: All ESOP data uses content-addressed hashing for immutable audit trail
8. Risks and Edge Cases
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Pool over-allocation (grants exceed pool capacity) | Med | High | Backend validation on every grant; pool remaining shares computed at query time |
| Vesting calculation drift from rounding | Low | Med | Use numeric precision; round consistently at display layer only |
| Board approval date backdating | Low | High | Audit log on plan creation; board approval document required |
| Orphaned options after plan deletion | Low | Med | Cascade checks: prevent plan deletion while active options exist |
| Exercise before vesting cliff | Low | High | Backend enforces vested-share check before exercise task creation |
9. Dependencies
| Dependency | Type | Status |
|---|---|---|
| SPEC 003: Cap Table | Required before | DRAFT |
| SPEC 005: Agreements | Integrates with | DRAFT |
| SPEC 012: Team Members and Roles | Required before | DRAFT |
| SPEC 007: Documents and DocGen | Integrates with | DRAFT |