Skip to main content

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:
equa-web/src/modules/esop/
├── components/   # Plans, pools, options, vesting schedule UI
├── services/     # API client for ESOP operations
├── store/        # State management for plans, pools, options
└── index.tsx
Backend module: ESOP entities and endpoints are handled within the captable module on the server side — there is no standalone esop server module. Plan, pool, option, and vesting schedule entities live alongside other cap-table entities in the persistence layer.
equa-server/modules/captable/    # Endpoints serving ESOP data
equa-server/modules/persistence/ # Entity definitions (Plans, Pools, Options, VestingSchedules)

3. Data Model

Entities

EntityDescriptionFile
PlansBoard-approved equity incentive plansequa-server/modules/persistence/src/entity/Plans.ts
PlansSecuritiesJoin table: plans ↔ securitiesequa-server/modules/persistence/src/entity/PlansSecurities.ts
PlansVestingSchedulesJoin table: plans ↔ vesting schedulesequa-server/modules/persistence/src/entity/PlansVestingSchedules.ts
PoolsOption pools allocated from a planequa-server/modules/persistence/src/entity/Pools.ts
OptionsIndividual option grants to membersequa-server/modules/persistence/src/entity/Options.ts
ContinuousVestingSchedulesTime-based vesting with cliff and frequencyequa-server/modules/persistence/src/entity/ContinuousVestingSchedules.ts
DiscreteVestingSchedulesMilestone / custom-date vestingequa-server/modules/persistence/src/entity/DiscreteVestingSchedules.ts
VestingEventsIndividual vesting events within a discrete scheduleequa-server/modules/persistence/src/entity/VestingEvents.ts
TasksExerciseOptionExercise-option workflow tasksequa-server/modules/persistence/src/entity/TasksExerciseOption.ts

Key Fields — Plans

FieldTypeNullableDescription
hashHashNoPrimary key (content-addressed)
organizationuuidNoOwning organization
namestringNoPlan display name
boardApprovalDatedateNoDate the board approved this plan
termYearssmallintNoPlan duration in years
approvedEquitiesjsonbYesApproved equity types and amounts
createdByuuidNoUser who created the plan
formatsmallintNoPlan format enum
boardApprovalDocumentHashYesReference to board resolution document
incentivePlanDocumentHashYesReference to the plan document itself

Key Fields — Pools

FieldTypeNullableDescription
organizationuuidNoOwning organization
securityHashNoSecurity type for this pool
sharesnumericNoTotal shares allocated to pool
pricePerSharenumericNoStrike / exercise price
vestingScheduleHashYesDefault vesting schedule
namestringNoPool display name
boardApprovalDatedateNoBoard approval date
optionsHashYesReference to associated options

Key Fields — Options

FieldTypeNullableDescription
organizationuuidNoOwning organization
securityHashNoSecurity type
memberuuidNoGrantee (team member)
sharesnumericNoNumber of options granted
startDatedateNoVesting start date
vestingScheduleHashYesVesting schedule for this grant
legendHashYesLegend / restriction text
notetextYesFree-text note

Key Fields — ContinuousVestingSchedules

FieldTypeNullableDescription
durationMonthssmallintNoTotal vesting duration
frequencyMonthssmallintNoVesting frequency (e.g. 1 = monthly)
cliffMonthssmallintNoCliff period in months
cliffAmountnumericNoAmount that vests at cliff
cliffAmountTypesmallintNoAbsolute shares or percentage
entityuuidNoOwning entity (organization)
namestringNoSchedule display name
vestsOnsmallintNoDay-of-month vesting occurs

Key Fields — DiscreteVestingSchedules

FieldTypeNullableDescription
eventsHashNoReference to ordered vesting events
entityuuidNoOwning entity
isValueAbsolutebooleanNoWhether event values are absolute shares or percentages
namestringNoSchedule display name

Key Fields — VestingEvents

FieldTypeNullableDescription
daysmallintNoDay of event
monthsmallintNoMonth of event
yearsmallintNoYear of event
valuenumericNoShares or percentage that vest

Key Fields — TasksExerciseOption

FieldTypeNullableDescription
iduuidNoPrimary key
optionHashNoOption being exercised
sharesnumericNoNumber of shares to exercise

Relationships

4. API Endpoints

Served by the captable module endpoints.
MethodPathAuthDescription
GET/v1/organizations/:orgId/plansSessionList all equity incentive plans
POST/v1/organizations/:orgId/plansSessionCreate a new plan
GET/v1/organizations/:orgId/plans/:hashSessionGet plan details
PUT/v1/organizations/:orgId/plans/:hashSessionUpdate a plan
DELETE/v1/organizations/:orgId/plans/:hashSessionDelete a plan
GET/v1/organizations/:orgId/poolsSessionList option pools
POST/v1/organizations/:orgId/poolsSessionCreate an option pool
GET/v1/organizations/:orgId/pools/:hashSessionGet pool details
PUT/v1/organizations/:orgId/pools/:hashSessionUpdate a pool
GET/v1/organizations/:orgId/optionsSessionList option grants
POST/v1/organizations/:orgId/optionsSessionGrant options to a member
GET/v1/organizations/:orgId/options/:hashSessionGet option grant details
PUT/v1/organizations/:orgId/options/:hashSessionUpdate an option grant
GET/v1/organizations/:orgId/vesting-schedulesSessionList vesting schedules
POST/v1/organizations/:orgId/vesting-schedulesSessionCreate a vesting schedule
POST/v1/organizations/:orgId/options/:hash/exerciseSessionExercise options

5. Frontend Components

ComponentFilePurpose
PlansPageequa-web/src/modules/esop/components/PlansPage.tsxList and manage equity incentive plans
PlanDetailequa-web/src/modules/esop/components/PlanDetail.tsxView/edit a single plan
PoolsPageequa-web/src/modules/esop/components/PoolsPage.tsxList and manage option pools
PoolDetailequa-web/src/modules/esop/components/PoolDetail.tsxView/edit a single pool
OptionsPageequa-web/src/modules/esop/components/OptionsPage.tsxList option grants
OptionGrantFormequa-web/src/modules/esop/components/OptionGrantForm.tsxGrant options to a member
VestingScheduleFormequa-web/src/modules/esop/components/VestingScheduleForm.tsxCreate/edit vesting schedules
VestingTimelineequa-web/src/modules/esop/components/VestingTimeline.tsxVisual vesting timeline

Routes

RouteComponentDescription
/organizations/:orgId/esop/plansPlansPagePlan listing
/organizations/:orgId/esop/plans/:hashPlanDetailSingle plan detail
/organizations/:orgId/esop/poolsPoolsPagePool listing
/organizations/:orgId/esop/pools/:hashPoolDetailSingle pool detail
/organizations/:orgId/esop/optionsOptionsPageOption grants listing

State Management

ESOP state is managed within the esop 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

RuleDescriptionEnforcement
ESOP-001A plan must have a board approval date before options can be granted under itBackend
ESOP-002Pool shares cannot exceed the plan’s approved equity amount for the associated security typeBackend
ESOP-003Option grant shares cannot exceed the remaining unallocated shares in the poolBackend
ESOP-004Cliff amount must not exceed total option grant sharesBackend
ESOP-005Vesting start date must be on or after the plan’s board approval dateBackend
ESOP-006Exercise shares cannot exceed the number of vested (but unexercised) shares at the time of exerciseBackend
ESOP-007termYears must be a positive integer (typically 10 years)Both
ESOP-008Content-addressed Hash fields are immutable once created; updates produce new hashesBackend

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 both startDate and totalValue.
PropertyDescription
frequencyNumber of months between Vesting Events
eventQuantityTotal number of Vesting Events (cliffs can reduce this)
vestsOnDayDay of the month, with three modes: Anniversary (day of startDate), First Day of Month, Last Day of Month
When a continuous schedule has no 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 cliffValue property overrides the value of the first post-cliff event, altering the vesting curve steepness
  • cliffValue can 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 - 1
  • day — Either an integer or lastDayOfMonth. 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

RiskLikelihoodImpactMitigation
Pool over-allocation (grants exceed pool capacity)MedHighBackend validation on every grant; pool remaining shares computed at query time
Vesting calculation drift from roundingLowMedUse numeric precision; round consistently at display layer only
Board approval date backdatingLowHighAudit log on plan creation; board approval document required
Orphaned options after plan deletionLowMedCascade checks: prevent plan deletion while active options exist
Exercise before vesting cliffLowHighBackend enforces vested-share check before exercise task creation

9. Dependencies

DependencyTypeStatus
SPEC 003: Cap TableRequired beforeDRAFT
SPEC 005: AgreementsIntegrates withDRAFT
SPEC 012: Team Members and RolesRequired beforeDRAFT
SPEC 007: Documents and DocGenIntegrates withDRAFT