SPEC 003 — Cap Table
| Field | Value |
|---|---|
| Status | DRAFT |
| Priority | P0 — Launch-Critical |
| Backend | equa-server/modules/captable/ |
| API | equa-server/modules/api/src/endpoints/captable-endpoints.ts |
| Frontend | equa-web/src/modules/captable/ |
Confluence sources incorporated: “Allocation Graphs,” “Capitalization V1” — see sections 6a and 6b below
1. Feature Purpose
The cap table is the core product of Equa. It tracks who owns what in an organization — shareholdings, security types, pricing, funding rounds, valuations, and ownership transfers. Every share issuance produces a signed certificate with legends. The cap table powers the fully-diluted ownership view, capital change history, and investor reporting.2. Current State (Verified)
2.1 Backend
| Component | Path |
|---|---|
| Module root | equa-server/modules/captable/ |
| API endpoints | equa-server/modules/api/src/endpoints/captable-endpoints.ts |
2.2 Frontend
| Component | Path |
|---|---|
| Cap table module | equa-web/src/modules/captable/ |
| Service layer | equa-web/src/service/services/captable |
| Redux state | capTableReducer in root-reducer.ts |
3. Data Model
Shareholdings
| Column | Type | Constraints |
|---|---|---|
| member | uuid | FK → Members |
| organization | uuid | FK → Organizations |
| shares | numeric | Precision-safe share count |
| securityType | hash | FK → SecurityTypes (content-addressed) |
| issueDate | date | |
| serialNumber | int | Certificate serial within org |
| shareholderName | varchar | Name as printed on certificate |
| legend | hash | FK → Legends (content-addressed) |
| exercised | boolean | Whether options/warrants have been exercised |
| legacyShareholding | boolean | Migrated from legacy system |
SecurityTypes
| Column | Type | Constraints |
|---|---|---|
| organization | uuid | FK → Organizations |
| shareClass | varchar | e.g. “Common”, “Series A Preferred” |
| shareType | smallint | Enum (common, preferred, options, warrants, etc.) |
| certifiedShares | boolean | Whether share certificates are issued |
| votingShares | boolean | Carries voting rights |
| price | numeric | Default price per share |
| name | varchar | Display name |
| sharePricing | hash | FK → SharePricingTable (content-addressed) |
| fractionalShares | boolean | Allows fractional ownership |
SecurityDetails
Related metadata for security types (structure follows SecurityTypes context).SecurityTypeSeniorityTable
Tracks liquidation preference ordering among security types within an organization.SecurityTypeSharesTable
Aggregated share counts per security type (authorized, outstanding, reserved).SharePricingTable
| Column | Type | Constraints |
|---|---|---|
| pricePerShare | numeric | |
| parValue | numeric | |
| originalIssuePrice | numeric | |
| conversionPrice | numeric | For convertible securities |
| conversionRate | numeric | Conversion ratio |
Holdings
| Column | Type | Constraints |
|---|---|---|
| entity | uuid | Organization or fund entity |
| owner | uuid | Member or entity that owns the holding |
| value | numeric | Share count or unit value |
| holdingType | varchar | Type classification |
| name | varchar | Display name |
| class | varchar | Share class label |
| issueDate | date | |
| pricePerUnit | numeric | |
| outstanding | numeric | Currently outstanding |
| authorized | numeric | Total authorized |
| fullyDiluted | numeric | Fully-diluted count |
CapitalChanges
| Column | Type | Constraints |
|---|---|---|
| mod | numeric | Change amount (positive or negative) |
| timestamp | timestamp | When the change occurred |
| type | uuid | FK → SecurityTypes or change-type reference |
| organization | uuid | FK → Organizations |
FundingRounds
| Column | Type | Constraints |
|---|---|---|
| id | uuid | PK |
| organization | uuid | FK → Organizations |
| number | int | Round sequence number |
| endDate | date | |
| totalCommon | numeric | Common shares in round |
| totalPreferred | numeric | Preferred shares in round |
| name | varchar | e.g. “Seed”, “Series A” |
Valuations
| Column | Type | Constraints |
|---|---|---|
| entity | uuid | FK → Organizations |
| issueDate | date | Valuation date |
| value | numeric | Valuation amount |
| units | varchar | Currency or unit label |
Transfers
| Column | Type | Constraints |
|---|---|---|
| inputs | hash | Content-addressed reference to source holdings |
| outputs | hash | Content-addressed reference to TransferOutputs |
TransferOutputs
| Column | Type | Constraints |
|---|---|---|
| holding | hash | Content-addressed holding reference |
| owner | uuid | FK → new owner (Member or entity) |
| value | numeric | Transferred amount |
Legends
| Column | Type | Constraints |
|---|---|---|
| content | text | Full legend text (content-addressed by hash) |
LegendMetas
| Column | Type | Constraints |
|---|---|---|
| legend | hash | FK → Legends |
| organization | uuid | FK → Organizations |
| name | varchar | Legend display name |
| author | uuid | FK → Users who created the legend |
Signatures
| Column | Type | Constraints |
|---|---|---|
| id | uuid | PK |
| shareholding | uuid | FK → Shareholdings |
| signatory | uuid | FK → Members |
| name | varchar | Signatory name as displayed |
| title | varchar | Signatory title |
4. API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/organizations/:id/captable | Yes | Full cap table for organization |
| GET | /api/v1/organizations/:id/shareholdings | Yes | List all shareholdings |
| POST | /api/v1/organizations/:id/shareholdings | Yes | Issue new shares |
| PUT | /api/v1/organizations/:id/shareholdings/:shId | Yes | Update a shareholding |
| DELETE | /api/v1/organizations/:id/shareholdings/:shId | Yes | Cancel / void a shareholding |
| GET | /api/v1/organizations/:id/security-types | Yes | List security types |
| POST | /api/v1/organizations/:id/security-types | Yes | Create a security type |
| PUT | /api/v1/organizations/:id/security-types/:stId | Yes | Update a security type |
| GET | /api/v1/organizations/:id/funding-rounds | Yes | List funding rounds |
| POST | /api/v1/organizations/:id/funding-rounds | Yes | Create a funding round |
| GET | /api/v1/organizations/:id/valuations | Yes | List valuations |
| POST | /api/v1/organizations/:id/valuations | Yes | Record a valuation |
| POST | /api/v1/organizations/:id/transfers | Yes | Execute a share transfer |
| GET | /api/v1/organizations/:id/capital-changes | Yes | Capital change history |
| GET | /api/v1/organizations/:id/legends | Yes | List legends |
| POST | /api/v1/organizations/:id/legends | Yes | Create a legend |
| GET | /api/v1/organizations/:id/signatures | Yes | List certificate signatures |
| POST | /api/v1/organizations/:id/signatures | Yes | Add a signature to a shareholding |
5. Frontend Components
| Component | Path | Description |
|---|---|---|
| Cap table module | equa-web/src/modules/captable/ | Cap table management, shareholding list, issuance forms |
| Shareholdings view | equa-web/src/modules/captable/ | Individual shareholding detail and certificate preview |
| Certificates | equa-web/src/modules/captable/ | Certificate generation with legends and signatures |
| Service layer | equa-web/src/service/services/captable | API client for cap table operations |
| Redux reducer | capTableReducer in root-reducer.ts | Client-side state for cap table data |
6. Business Rules
- Numeric precision — Shares, prices, and valuations use
numerictype (arbitrary precision) to avoid floating-point errors in financial calculations. - Content-addressed immutability — SecurityTypes, Legends, SharePricing, and Transfers reference content by hash, ensuring historical records cannot be silently altered.
- Serial numbers — Each shareholding within an organization receives a sequential
serialNumberfor certificate identification. - Fractional shares — Allowed only when
SecurityTypes.fractionalShares = truefor the relevant security type. - Transfer integrity — Transfers use input/output hashes; the sum of output values must equal the input holding value (no shares created or destroyed).
- Exercised flag — Options and warrants track
exercised = trueonce converted, preventing double exercise. - Seniority ordering —
SecurityTypeSeniorityTabledetermines liquidation preference order among security types. - Fully-diluted calculation — Holdings track
outstanding,authorized, andfullyDilutedseparately to support both basic and diluted ownership views. - Legends on certificates — Every shareholding references a legend hash; legends are immutable once assigned to issued certificates.
- Signatures required — Certificates require at least one Signature record linking a signatory member with name and title.
- Legacy flag —
legacyShareholding = truemarks shareholdings imported from the previous system for migration tracking.
6a. Allocation Graph Model (from Confluence KnowledgeBase)
Source: Confluence KnowledgeBase — Allocation Graphs (by Christopher Johnson)An entity can be divided into units, which are grouped into pools. Pools can be subdivided into child pools, forming a hierarchy called an allocation graph. An allocation graph is itself an entity. Node properties:
value— the amount of units allocated to the nodechildren— zero or more child nodes (sum of children’s values must not exceed parent’s value)parent— every node except the root must have exactly one parent
- Outstanding — issued and held by shareholders
- External Reserved — allocated for external parties (investors, advisors)
- Treasury (Internal Reserved) — held internally by the organization
6b. Capitalization V1 User Stories (from Confluence KnowledgeBase)
Source: Confluence KnowledgeBase — Capitalization V1 (by Christopher Johnson)User stories (outstanding holdings only):
- Transfer shares without affecting capital contributions of the source holding
- Edit capital contributions of an active holding
- Cancel a holding and preserve its capital contributions
- Cancel a holding and its capital contributions
- Edit capital contributions of a cancelled holding
- Edit capital contribution of a holding that was transferred (effectively cancelled)
- When a holding is cancelled or transferred, capital contributions remain active by default
- To cancel both a holding and its contributions: set contributions to zero, then cancel the holding
- Users with cap table edit permission can edit cancelled holdings (known UI bug: “Resource Not Found” error)
- Capital contribution fields in the Transfer form are deprecated and should not be used
7. Acceptance Criteria
- Organization owner can create security types with class, pricing, voting, and fractional share settings
- Shares can be issued to members with correct serial number auto-increment
- Share pricing (price per share, par value, original issue price, conversion terms) is stored accurately
- Shareholdings display on the cap table with correct ownership percentages
- Fully-diluted view includes outstanding, authorized, and fully-diluted counts
- Share transfers move ownership atomically (inputs → outputs) with value conservation
- Options/warrants can be marked as exercised; double exercise is prevented
- Funding rounds track common and preferred totals with round name and end date
- Valuations are recorded per entity with date, value, and unit
- Capital changes history logs every issuance, transfer, and cancellation
- Legends are immutable and content-addressed
- Certificates include legend text and at least one signature
- Security type seniority ordering is configurable
- Fractional shares are rejected when
fractionalShares = false - Redux state stays in sync with server after cap table mutations
8. Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Numeric precision loss in frontend JavaScript | Incorrect ownership percentages | Use string-based numeric transport; parse with decimal library on client |
| Transfer value mismatch (inputs ≠ outputs) | Phantom shares created or destroyed | Server-side validation: sum(outputs.value) must equal input holding value |
| Concurrent issuance creates duplicate serial numbers | Certificate numbering conflict | Use database sequence or serialized transaction for serial assignment |
| Content-addressed hash collision | Wrong legend or pricing served | SHA-256 makes collision negligible; add unique constraint on hash |
| Legacy shareholding flag not consistently checked | Incorrect display for migrated data | Centralize legacy handling in service layer |
| Large cap tables degrade query performance | Slow page loads | Paginate shareholdings; index on (organization, securityType) |
| Missing signature on issued certificate | Legally incomplete document | Enforce minimum one signature at issuance time via validation |