diff --git a/API-SPEC.yaml b/API-SPEC.yaml new file mode 100644 index 0000000..c58d47c --- /dev/null +++ b/API-SPEC.yaml @@ -0,0 +1,2909 @@ +openapi: 3.0.3 +info: + title: Dealspace API + description: | + M&A deal management platform API. Investment Banks, Sellers, and Buyers collaborate + on a structured request-and-answer workflow system. + + ## Authentication + All endpoints except `/health` and `/api/auth/*` require a valid Bearer token. + Tokens are JWT (HS256) with 1-hour access token lifetime and 7-day refresh token lifetime. + + ## RBAC Model + Access is scoped to projects and workstreams. Roles determine permissions: + - **ib_admin** — Full control over project and all workstreams + - **ib_member** — Manage requests and vet answers in assigned workstreams + - **seller_admin** — Manage seller team, see all seller-directed requests + - **seller_member** — Answer requests in assigned workstreams + - **buyer_admin** — Manage buyer team, access data room + - **buyer_member** — Submit requests, view published answers + - **observer** — Read-only access to assigned workstreams + + ## Error Responses + All errors follow a consistent format: + ```json + {"error": "Human-readable message", "code": "ERROR_CODE", "details": {}} + ``` + + ## Rate Limiting + Endpoints are rate-limited per user, IP, and project. Headers `X-RateLimit-Limit`, + `X-RateLimit-Remaining`, and `Retry-After` are included in responses. + version: 1.0.0 + contact: + name: Dealspace Support + email: support@muskepo.com + license: + name: Proprietary + +servers: + - url: https://muskepo.com/api + description: Production + - url: http://localhost:8080/api + description: Development + +tags: + - name: Auth + description: Authentication and MFA + - name: Projects + description: Top-level deal containers + - name: Workstreams + description: RBAC-anchored workflow containers (Finance, Legal, IT, etc.) + - name: Request Lists + description: Named collections of requests within a workstream + - name: Requests + description: Individual request items + - name: Answers + description: Responses to requests + - name: Matches + description: AI-suggested answer-to-request matching + - name: Objects + description: File upload/download with watermarking + - name: Tasks + description: Personal task inbox + - name: Access + description: RBAC grants + - name: Invites + description: Project invitations + - name: Audit + description: Security audit log + - name: Health + description: Service health check + +paths: + # ============================================================================ + # AUTH ENDPOINTS + # ============================================================================ + + /auth/login: + post: + tags: [Auth] + summary: Authenticate with email and password + description: | + Returns access and refresh tokens. If MFA is required but not yet verified, + the token will have `mfa: false` and can only be used on `/auth/mfa/*` endpoints. + + **Rate limit:** 5 requests/minute per IP + operationId: login + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [email, password] + properties: + email: + type: string + format: email + example: user@example.com + password: + type: string + format: password + minLength: 8 + fingerprint: + type: object + description: Device fingerprint for session binding + properties: + userAgent: + type: string + acceptLanguage: + type: string + timezone: + type: string + responses: + '200': + description: Authentication successful + content: + application/json: + schema: + $ref: '#/components/schemas/AuthTokens' + '401': + description: Invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: Rate limit exceeded + headers: + Retry-After: + schema: + type: integer + description: Seconds until rate limit resets + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /auth/refresh: + post: + tags: [Auth] + summary: Rotate refresh token + description: | + Exchange a valid refresh token for new access and refresh tokens. + The old refresh token is invalidated (rotation). Fingerprint must match + the original session. + + **Rate limit:** 60 requests/minute per user + operationId: refreshToken + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [refresh_token] + properties: + refresh_token: + type: string + fingerprint: + type: object + properties: + userAgent: + type: string + acceptLanguage: + type: string + timezone: + type: string + responses: + '200': + description: Tokens rotated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AuthTokens' + '401': + description: Invalid or expired refresh token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /auth/logout: + post: + tags: [Auth] + summary: Revoke current session + description: | + Invalidates the current access token and all associated refresh tokens. + + **Rate limit:** 60 requests/minute per user + operationId: logout + security: + - bearerAuth: [] + responses: + '204': + description: Session revoked successfully + '401': + description: Invalid or expired token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /auth/mfa/setup: + post: + tags: [Auth] + summary: Initiate TOTP MFA setup + description: | + Generates a new TOTP secret and returns a QR code URI for authenticator apps. + Also returns recovery codes (store securely, shown only once). + + **RBAC:** Any authenticated user + **Rate limit:** 5 requests/minute per user + operationId: mfaSetup + security: + - bearerAuth: [] + responses: + '200': + description: MFA setup initiated + content: + application/json: + schema: + type: object + properties: + secret: + type: string + description: Base32-encoded TOTP secret (for manual entry) + qr_uri: + type: string + format: uri + description: otpauth:// URI for QR code generation + recovery_codes: + type: array + items: + type: string + description: 10 single-use recovery codes + '400': + description: MFA already enabled + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /auth/mfa/verify: + post: + tags: [Auth] + summary: Verify TOTP code + description: | + Completes MFA setup (if pending) or verifies MFA during login. + On successful verification, returns a new token with `mfa: true`. + + **Rate limit:** 5 requests/minute per user (prevents brute force) + operationId: mfaVerify + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [code] + properties: + code: + type: string + pattern: '^\d{6}$' + description: 6-digit TOTP code from authenticator app + responses: + '200': + description: MFA verified + content: + application/json: + schema: + $ref: '#/components/schemas/AuthTokens' + '401': + description: Invalid TOTP code + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /invites/accept: + post: + tags: [Auth] + summary: Accept invitation and create account + description: | + Accepts an invite token. If the user does not exist, creates a new account. + If the user exists (by email), grants access to the invited project/workstream. + + **Rate limit:** 10 requests/minute per IP + operationId: acceptInvite + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [token] + properties: + token: + type: string + description: Invite token from email + name: + type: string + description: User's full name (required if creating new account) + password: + type: string + format: password + minLength: 8 + description: Password (required if creating new account) + responses: + '200': + description: Invitation accepted + content: + application/json: + schema: + type: object + properties: + user_id: + type: string + format: uuid + project_id: + type: string + format: uuid + role: + type: string + tokens: + $ref: '#/components/schemas/AuthTokens' + '400': + description: Invalid, expired, or already-used invite + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # ============================================================================ + # PROJECT ENDPOINTS + # ============================================================================ + + /projects: + get: + tags: [Projects] + summary: List all projects for the authenticated user + description: | + Returns all projects the user has access to, based on their role grants. + + **RBAC:** Any authenticated user + **Rate limit:** 300 requests/minute per user + operationId: listProjects + security: + - bearerAuth: [] + parameters: + - name: stage + in: query + schema: + type: string + enum: [pre_dataroom, dataroom, closed] + description: Filter by deal stage + - name: limit + in: query + schema: + type: integer + default: 50 + maximum: 100 + - name: offset + in: query + schema: + type: integer + default: 0 + responses: + '200': + description: List of projects + content: + application/json: + schema: + type: object + properties: + projects: + type: array + items: + $ref: '#/components/schemas/Project' + total: + type: integer + limit: + type: integer + offset: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + + post: + tags: [Projects] + summary: Create a new project + description: | + Creates a new project. The creator automatically becomes `ib_admin`. + + **RBAC:** Any authenticated user (typically IB initiates deals) + **Rate limit:** 10 requests/minute per user + operationId: createProject + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectCreate' + responses: + '201': + description: Project created + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + + /projects/{id}: + parameters: + - $ref: '#/components/parameters/ProjectId' + + get: + tags: [Projects] + summary: Get project details + description: | + Returns project metadata and summary statistics. + + **RBAC:** Any role with access to the project + **Rate limit:** 300 requests/minute per user + operationId: getProject + security: + - bearerAuth: [] + responses: + '200': + description: Project details + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + patch: + tags: [Projects] + summary: Update project + description: | + Updates project metadata (name, stage, theme, etc.). + + **RBAC:** `ib_admin` only + **Rate limit:** 60 requests/minute per user + operationId: updateProject + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectUpdate' + responses: + '200': + description: Project updated + headers: + ETag: + schema: + type: string + description: Updated version tag for concurrency control + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '409': + $ref: '#/components/responses/Conflict' + '412': + $ref: '#/components/responses/PreconditionFailed' + + delete: + tags: [Projects] + summary: Soft delete project + description: | + Marks the project as deleted. Data is retained for compliance but + project becomes inaccessible to all users. + + **RBAC:** `ib_admin` only + **Rate limit:** 10 requests/minute per user + operationId: deleteProject + security: + - bearerAuth: [] + responses: + '204': + description: Project deleted + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + # ============================================================================ + # WORKSTREAM ENDPOINTS + # ============================================================================ + + /projects/{id}/workstreams: + parameters: + - $ref: '#/components/parameters/ProjectId' + + get: + tags: [Workstreams] + summary: List workstreams in project + description: | + Returns all workstreams the user has access to within the project. + + **RBAC:** Any role with project access (filtered by workstream-level access) + **Rate limit:** 300 requests/minute per user + operationId: listWorkstreams + security: + - bearerAuth: [] + responses: + '200': + description: List of workstreams + content: + application/json: + schema: + type: object + properties: + workstreams: + type: array + items: + $ref: '#/components/schemas/Workstream' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + post: + tags: [Workstreams] + summary: Create workstream + description: | + Creates a new workstream within the project. Workstreams are RBAC anchors. + + **RBAC:** `ib_admin` only + **Rate limit:** 60 requests/minute per user + operationId: createWorkstream + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkstreamCreate' + responses: + '201': + description: Workstream created + content: + application/json: + schema: + $ref: '#/components/schemas/Workstream' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + + patch: + tags: [Workstreams] + summary: Update workstream + description: | + Updates workstream metadata (name, description). + + **RBAC:** `ib_admin` or `ib_member` with access to this workstream + **Rate limit:** 60 requests/minute per user + operationId: updateWorkstream + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkstreamUpdate' + responses: + '200': + description: Workstream updated + content: + application/json: + schema: + $ref: '#/components/schemas/Workstream' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + # ============================================================================ + # REQUEST LIST ENDPOINTS + # ============================================================================ + + /projects/{id}/workstreams/{wsId}/lists: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + + get: + tags: [Request Lists] + summary: List request lists in workstream + description: | + Returns all request lists within the workstream. + + **RBAC:** Any role with workstream access + **Rate limit:** 300 requests/minute per user + operationId: listRequestLists + security: + - bearerAuth: [] + responses: + '200': + description: List of request lists + content: + application/json: + schema: + type: object + properties: + lists: + type: array + items: + $ref: '#/components/schemas/RequestList' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + post: + tags: [Request Lists] + summary: Create request list + description: | + Creates a new named collection of requests within the workstream. + + **RBAC:** `ib_admin`, `ib_member` + **Rate limit:** 60 requests/minute per user + operationId: createRequestList + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RequestListCreate' + responses: + '201': + description: Request list created + content: + application/json: + schema: + $ref: '#/components/schemas/RequestList' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ============================================================================ + # REQUEST ENDPOINTS + # ============================================================================ + + /projects/{id}/workstreams/{wsId}/requests: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + + get: + tags: [Requests] + summary: List requests in workstream + description: | + Returns requests in the workstream. Supports filtering by assignee, status, and stage. + + **RBAC:** Any role with workstream access. Pre-dataroom entries invisible to buyers. + **Rate limit:** 300 requests/minute per user + operationId: listRequests + security: + - bearerAuth: [] + parameters: + - name: assignee + in: query + schema: + type: string + description: Filter by assignee. Use "me" for current user. + - name: status + in: query + schema: + type: string + enum: [open, assigned, answered, vetted, published, closed] + description: Filter by status + - name: stage + in: query + schema: + type: string + enum: [pre_dataroom, dataroom, closed] + description: Filter by deal stage + - name: list_id + in: query + schema: + type: string + format: uuid + description: Filter by request list + - name: priority + in: query + schema: + type: string + enum: [high, normal, low] + - name: overdue + in: query + schema: + type: boolean + description: Only return requests past due date + - name: limit + in: query + schema: + type: integer + default: 50 + maximum: 100 + - name: offset + in: query + schema: + type: integer + default: 0 + responses: + '200': + description: List of requests + content: + application/json: + schema: + type: object + properties: + requests: + type: array + items: + $ref: '#/components/schemas/Request' + total: + type: integer + limit: + type: integer + offset: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + post: + tags: [Requests] + summary: Create request + description: | + Creates a new request in the workstream. IB/Seller create for seller action, + Buyers create via data room (routed to IB). + + **RBAC:** `ib_admin`, `ib_member`, `buyer_admin`, `buyer_member` + **Rate limit:** 60 requests/minute per user + operationId: createRequest + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RequestCreate' + responses: + '201': + description: Request created + content: + application/json: + schema: + $ref: '#/components/schemas/Request' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/requests/{reqId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + + get: + tags: [Requests] + summary: Get request details + description: | + Returns full request details including routing chain (visible to IB only). + + **RBAC:** Any role with workstream access + **Rate limit:** 300 requests/minute per user + operationId: getRequest + security: + - bearerAuth: [] + responses: + '200': + description: Request details + headers: + ETag: + schema: + type: string + description: Version tag for concurrency control + content: + application/json: + schema: + $ref: '#/components/schemas/Request' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + patch: + tags: [Requests] + summary: Update request + description: | + Updates request metadata (title, body, priority, due_date, assigned_to, status). + Use `If-Match` header with ETag for optimistic concurrency control. + + **RBAC:** `ib_admin`, `ib_member`, `seller_admin`, `seller_member` (own assignments) + **Rate limit:** 60 requests/minute per user + operationId: updateRequest + security: + - bearerAuth: [] + parameters: + - name: If-Match + in: header + schema: + type: string + description: ETag from previous GET for concurrency control + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RequestUpdate' + responses: + '200': + description: Request updated + headers: + ETag: + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Request' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '409': + $ref: '#/components/responses/Conflict' + '412': + $ref: '#/components/responses/PreconditionFailed' + + /projects/{id}/workstreams/{wsId}/requests/{reqId}/forward: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + + post: + tags: [Requests] + summary: Forward request in routing chain + description: | + Forwards the request to another user. Creates a routing chain hop. + The `return_to_id` is set automatically so completion returns to the forwarder. + + **RBAC:** Current assignee or `ib_admin` + **Rate limit:** 60 requests/minute per user + operationId: forwardRequest + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [to_user_id] + properties: + to_user_id: + type: string + format: uuid + description: User to forward the request to + message: + type: string + description: Optional message for the recipient + responses: + '200': + description: Request forwarded + content: + application/json: + schema: + $ref: '#/components/schemas/Request' + '400': + description: Invalid recipient + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/requests/{reqId}/complete: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + + post: + tags: [Requests] + summary: Mark request as complete + description: | + Marks the request as completed by the current assignee. If `return_to_id` is set, + the request is automatically routed back to that user's inbox. Otherwise, it moves + to the final status. + + **RBAC:** Current assignee only + **Rate limit:** 60 requests/minute per user + operationId: completeRequest + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: Optional completion note + responses: + '200': + description: Request completed and routed + content: + application/json: + schema: + $ref: '#/components/schemas/Request' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ============================================================================ + # ANSWER ENDPOINTS + # ============================================================================ + + /projects/{id}/workstreams/{wsId}/answers: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + + post: + tags: [Answers] + summary: Create an answer + description: | + Creates a new answer in the workstream. Answers can be linked to one or more + requests. Initially created in `draft` status. + + **RBAC:** `seller_admin`, `seller_member`, `ib_admin`, `ib_member` + **Rate limit:** 60 requests/minute per user + operationId: createAnswer + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AnswerCreate' + responses: + '201': + description: Answer created + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/answers/{ansId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/AnswerId' + + get: + tags: [Answers] + summary: Get answer details + description: | + Returns full answer details including linked requests and files. + + **RBAC:** Depends on answer status. Draft/submitted visible to IB+seller. + Published visible to all with workstream access. + **Rate limit:** 300 requests/minute per user + operationId: getAnswer + security: + - bearerAuth: [] + responses: + '200': + description: Answer details + headers: + ETag: + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + patch: + tags: [Answers] + summary: Update answer + description: | + Updates answer content. Only allowed in `draft` or `rejected` status. + + **RBAC:** Answer creator, `seller_admin`, or `ib_admin` + **Rate limit:** 60 requests/minute per user + operationId: updateAnswer + security: + - bearerAuth: [] + parameters: + - name: If-Match + in: header + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AnswerUpdate' + responses: + '200': + description: Answer updated + headers: + ETag: + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '409': + $ref: '#/components/responses/Conflict' + + /projects/{id}/workstreams/{wsId}/answers/{ansId}/submit: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/AnswerId' + + post: + tags: [Answers] + summary: Submit answer for vetting + description: | + Submits the answer for IB review. Transitions status from `draft` to `submitted`. + + **RBAC:** Answer creator, `seller_admin` + **Rate limit:** 60 requests/minute per user + operationId: submitAnswer + security: + - bearerAuth: [] + responses: + '200': + description: Answer submitted for vetting + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '400': + description: Answer not in draft status + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/answers/{ansId}/approve: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/AnswerId' + + post: + tags: [Answers] + summary: Approve answer + description: | + IB approves the answer. Transitions status from `submitted` to `approved`. + Answer is now ready for publishing to data room. + + **RBAC:** `ib_admin`, `ib_member` with workstream access + **Rate limit:** 60 requests/minute per user + operationId: approveAnswer + security: + - bearerAuth: [] + responses: + '200': + description: Answer approved + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '400': + description: Answer not in submitted status + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/answers/{ansId}/reject: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/AnswerId' + + post: + tags: [Answers] + summary: Reject answer + description: | + IB rejects the answer with a reason. Transitions status from `submitted` to `rejected`. + Seller can then edit and resubmit. + + **RBAC:** `ib_admin`, `ib_member` with workstream access + **Rate limit:** 60 requests/minute per user + operationId: rejectAnswer + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: + type: string + minLength: 1 + description: Explanation for why the answer was rejected + responses: + '200': + description: Answer rejected + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '400': + description: Answer not in submitted status or missing reason + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/answers/{ansId}/publish: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/AnswerId' + + post: + tags: [Answers] + summary: Publish answer to data room + description: | + Publishes an approved answer to the data room. All linked requests are updated + and broadcast notifications sent to all requesters who asked equivalent questions. + + **RBAC:** `ib_admin`, `ib_member` with workstream access + **Rate limit:** 60 requests/minute per user + operationId: publishAnswer + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + type: object + properties: + broadcast_to: + type: string + enum: [linked_requesters, all_workstream, all_dataroom] + default: linked_requesters + description: | + Broadcast scope: + - `linked_requesters`: Only users who submitted linked requests + - `all_workstream`: All users with workstream access + - `all_dataroom`: All users with any data room access + responses: + '200': + description: Answer published + content: + application/json: + schema: + $ref: '#/components/schemas/Answer' + '400': + description: Answer not in approved status + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ============================================================================ + # AI MATCHING ENDPOINTS + # ============================================================================ + + /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + + get: + tags: [Matches] + summary: Get AI-suggested answer matches + description: | + Returns published answers that the AI has identified as potentially satisfying + this request (cosine similarity >= 0.72). Human confirmation is required before + linking. + + **RBAC:** `ib_admin`, `ib_member` + **Rate limit:** 20 requests/minute per user (embedding cost control) + operationId: getMatches + security: + - bearerAuth: [] + parameters: + - name: min_score + in: query + schema: + type: number + minimum: 0 + maximum: 1 + default: 0.72 + description: Minimum similarity score threshold + - name: limit + in: query + schema: + type: integer + default: 10 + maximum: 25 + responses: + '200': + description: List of suggested matches + content: + application/json: + schema: + type: object + properties: + matches: + type: array + items: + $ref: '#/components/schemas/AnswerMatch' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches/{answerId}/confirm: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + - $ref: '#/components/parameters/AnswerIdPath' + + post: + tags: [Matches] + summary: Confirm AI match + description: | + Human confirms that the suggested answer satisfies this request. + Creates a confirmed link in `answer_links` and updates request status. + Triggers broadcast to the requester. + + **RBAC:** `ib_admin`, `ib_member` + **Rate limit:** 60 requests/minute per user + operationId: confirmMatch + security: + - bearerAuth: [] + responses: + '200': + description: Match confirmed + content: + application/json: + schema: + type: object + properties: + request: + $ref: '#/components/schemas/Request' + answer: + $ref: '#/components/schemas/Answer' + link_id: + type: string + format: uuid + '400': + description: Answer not published or already linked + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches/{answerId}/reject: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/WorkstreamId' + - $ref: '#/components/parameters/RequestId' + - $ref: '#/components/parameters/AnswerIdPath' + + post: + tags: [Matches] + summary: Reject AI match + description: | + Human rejects the AI suggestion. The answer will not be suggested again for + this request (stored in `answer_links` with rejected flag). + + **RBAC:** `ib_admin`, `ib_member` + **Rate limit:** 60 requests/minute per user + operationId: rejectMatch + security: + - bearerAuth: [] + responses: + '200': + description: Match rejected + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Match rejected and will not be suggested again + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ============================================================================ + # FILE/OBJECT ENDPOINTS + # ============================================================================ + + /projects/{id}/objects: + parameters: + - $ref: '#/components/parameters/ProjectId' + + post: + tags: [Objects] + summary: Upload file + description: | + Uploads a file to the project's object store. Files are encrypted at rest. + Returns an objectId that can be referenced in answers. + + **RBAC:** `ib_admin`, `ib_member`, `seller_admin`, `seller_member` + **Rate limit:** 10 uploads/minute per user + **Max size:** 100MB per file + operationId: uploadObject + security: + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: + type: string + format: binary + filename: + type: string + description: Override filename (defaults to uploaded name) + description: + type: string + description: File description for search + responses: + '201': + description: File uploaded + content: + application/json: + schema: + type: object + properties: + object_id: + type: string + pattern: '^[a-f0-9]{16}$' + description: Content-addressable object ID + filename: + type: string + size: + type: integer + description: File size in bytes + mime_type: + type: string + uploaded_at: + type: string + format: date-time + '400': + description: Invalid file or missing data + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '413': + description: File too large + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /projects/{id}/objects/{objectId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ObjectId' + + get: + tags: [Objects] + summary: Download file with watermark + description: | + Downloads a file with dynamic watermark applied. Watermarks include: + - User name and organization + - Timestamp + - CONFIDENTIAL marking + + PDF, Word, Excel, and images have visible watermarks. + Other files are encrypted downloads only. + + **RBAC:** Any role with access to the answer containing this file + **Rate limit:** 50 downloads/minute per user (exfiltration protection) + operationId: downloadObject + security: + - bearerAuth: [] + parameters: + - name: preview + in: query + schema: + type: boolean + default: false + description: Return preview (lower quality) instead of full file + responses: + '200': + description: File content with watermark + headers: + Content-Disposition: + schema: + type: string + example: 'attachment; filename="document.pdf"' + Content-Type: + schema: + type: string + X-Watermark-Applied: + schema: + type: string + enum: [visible, encrypted, none] + content: + '*/*': + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + delete: + tags: [Objects] + summary: Delete file + description: | + Deletes a file from the object store. File must not be referenced by any + published answer. + + **RBAC:** File uploader, `ib_admin`, `seller_admin` + **Rate limit:** 60 requests/minute per user + operationId: deleteObject + security: + - bearerAuth: [] + responses: + '204': + description: File deleted + '400': + description: File is referenced by published answer + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + # ============================================================================ + # TASK ENDPOINTS + # ============================================================================ + + /tasks: + get: + tags: [Tasks] + summary: List personal tasks + description: | + Returns all tasks (requests) assigned to the current user across all projects. + This is the user's personal inbox. + + **RBAC:** Any authenticated user + **Rate limit:** 300 requests/minute per user + operationId: listTasks + security: + - bearerAuth: [] + parameters: + - name: status + in: query + schema: + type: string + enum: [open, assigned, answered, vetted, published, closed] + description: Filter by status + - name: overdue + in: query + schema: + type: boolean + description: Only return tasks past due date + - name: project_id + in: query + schema: + type: string + format: uuid + description: Filter by project + - name: priority + in: query + schema: + type: string + enum: [high, normal, low] + - name: limit + in: query + schema: + type: integer + default: 50 + maximum: 100 + - name: offset + in: query + schema: + type: integer + default: 0 + responses: + '200': + description: List of tasks + content: + application/json: + schema: + type: object + properties: + tasks: + type: array + items: + $ref: '#/components/schemas/Task' + total: + type: integer + limit: + type: integer + offset: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + + # ============================================================================ + # ACCESS ENDPOINTS + # ============================================================================ + + /projects/{id}/access: + parameters: + - $ref: '#/components/parameters/ProjectId' + + get: + tags: [Access] + summary: List access grants + description: | + Returns all access grants for the project. + + **RBAC:** `ib_admin`, `seller_admin` (own org), `buyer_admin` (own org) + **Rate limit:** 300 requests/minute per user + operationId: listAccess + security: + - bearerAuth: [] + parameters: + - name: role + in: query + schema: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + description: Filter by role + - name: workstream_id + in: query + schema: + type: string + format: uuid + description: Filter by workstream + responses: + '200': + description: List of access grants + content: + application/json: + schema: + type: object + properties: + grants: + type: array + items: + $ref: '#/components/schemas/AccessGrant' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + post: + tags: [Access] + summary: Grant access + description: | + Grants a role to a user for the project and/or specific workstream. + Users can only grant roles at or below their level. + IB can grant any role. Seller/Buyer can only grant within their org type. + + **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin` (with hierarchy constraints) + **Rate limit:** 60 requests/minute per user + operationId: grantAccess + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AccessGrantCreate' + responses: + '201': + description: Access granted + content: + application/json: + schema: + $ref: '#/components/schemas/AccessGrant' + '400': + description: Invalid input or role hierarchy violation + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/access/{grantId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/GrantId' + + delete: + tags: [Access] + summary: Revoke access + description: | + Revokes an access grant. All active sessions for the affected user are immediately + invalidated for this project. + + **RBAC:** `ib_admin`, or admin of same org type who granted the access + **Rate limit:** 60 requests/minute per user + operationId: revokeAccess + security: + - bearerAuth: [] + parameters: + - name: reason + in: query + schema: + type: string + description: Audit trail reason for revocation + responses: + '204': + description: Access revoked + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + # ============================================================================ + # INVITE ENDPOINTS + # ============================================================================ + + /projects/{id}/invites: + parameters: + - $ref: '#/components/parameters/ProjectId' + + get: + tags: [Invites] + summary: List pending invites + description: | + Returns all pending invitations for the project. + + **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin` + **Rate limit:** 300 requests/minute per user + operationId: listInvites + security: + - bearerAuth: [] + parameters: + - name: status + in: query + schema: + type: string + enum: [pending, accepted, expired, revoked] + responses: + '200': + description: List of invites + content: + application/json: + schema: + type: object + properties: + invites: + type: array + items: + $ref: '#/components/schemas/Invite' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + post: + tags: [Invites] + summary: Create invite + description: | + Creates an invitation for a user to join the project with a specific role. + Invites are scoped to a project and optionally to specific workstreams. + Invite token expires in 72 hours. + + **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin` (with role hierarchy constraints) + **Rate limit:** 30 requests/minute per user + operationId: createInvite + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InviteCreate' + responses: + '201': + description: Invite created and sent + content: + application/json: + schema: + $ref: '#/components/schemas/Invite' + '400': + description: Invalid input or role hierarchy violation + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /projects/{id}/invites/{inviteId}: + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/InviteId' + + delete: + tags: [Invites] + summary: Revoke invite + description: | + Revokes a pending invitation. If already accepted, use access revocation instead. + + **RBAC:** Invite creator or `ib_admin` + **Rate limit:** 60 requests/minute per user + operationId: revokeInvite + security: + - bearerAuth: [] + responses: + '204': + description: Invite revoked + '400': + description: Invite already accepted + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + + # ============================================================================ + # AUDIT ENDPOINT + # ============================================================================ + + /projects/{id}/audit: + parameters: + - $ref: '#/components/parameters/ProjectId' + + get: + tags: [Audit] + summary: Get audit log + description: | + Returns paginated audit log entries for the project. Includes all security-relevant + events: access changes, file downloads, status transitions, logins. + + Audit entries are hash-chained for tamper evidence. + + **RBAC:** `ib_admin` only + **Rate limit:** 30 requests/minute per user + operationId: getAuditLog + security: + - bearerAuth: [] + parameters: + - name: action + in: query + schema: + type: string + description: Filter by action type (e.g., "file.downloaded", "access.granted") + - name: actor_id + in: query + schema: + type: string + format: uuid + description: Filter by actor + - name: target_id + in: query + schema: + type: string + format: uuid + description: Filter by target entry/user/file + - name: from + in: query + schema: + type: string + format: date-time + description: Start of time range + - name: to + in: query + schema: + type: string + format: date-time + description: End of time range + - name: limit + in: query + schema: + type: integer + default: 100 + maximum: 500 + - name: offset + in: query + schema: + type: integer + default: 0 + responses: + '200': + description: Audit log entries + content: + application/json: + schema: + type: object + properties: + entries: + type: array + items: + $ref: '#/components/schemas/AuditEntry' + total: + type: integer + limit: + type: integer + offset: + type: integer + chain_verified: + type: boolean + description: Whether hash chain integrity was verified + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ============================================================================ + # HEALTH ENDPOINT + # ============================================================================ + + /health: + get: + tags: [Health] + summary: Service health check + description: | + Returns service health status. No authentication required. + Suitable for load balancer and monitoring probes. + operationId: healthCheck + security: [] + responses: + '200': + description: Service healthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [healthy, degraded] + version: + type: string + example: "1.0.0" + uptime: + type: integer + description: Uptime in seconds + checks: + type: object + properties: + database: + type: string + enum: [ok, error] + object_store: + type: string + enum: [ok, error] + redis: + type: string + enum: [ok, error] + '503': + description: Service unhealthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [unhealthy] + errors: + type: array + items: + type: string + +# ============================================================================== +# COMPONENTS +# ============================================================================== + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: | + JWT token from `/auth/login` or `/auth/refresh`. + Token contains: sub (user_id), org, roles[], mfa, sid, fp, exp. + + parameters: + ProjectId: + name: id + in: path + required: true + schema: + type: string + format: uuid + description: Project ID + + WorkstreamId: + name: wsId + in: path + required: true + schema: + type: string + format: uuid + description: Workstream ID + + RequestId: + name: reqId + in: path + required: true + schema: + type: string + format: uuid + description: Request ID + + AnswerId: + name: ansId + in: path + required: true + schema: + type: string + format: uuid + description: Answer ID + + AnswerIdPath: + name: answerId + in: path + required: true + schema: + type: string + format: uuid + description: Answer ID for matching + + ObjectId: + name: objectId + in: path + required: true + schema: + type: string + pattern: '^[a-f0-9]{16}$' + description: Content-addressable object ID + + GrantId: + name: grantId + in: path + required: true + schema: + type: string + format: uuid + description: Access grant ID + + InviteId: + name: inviteId + in: path + required: true + schema: + type: string + format: uuid + description: Invite ID + + responses: + Unauthorized: + description: Authentication required or token invalid + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Invalid or expired authentication token" + code: "UNAUTHORIZED" + details: {} + + Forbidden: + description: Insufficient permissions for this operation + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "You do not have permission to perform this action" + code: "FORBIDDEN" + details: + required_role: "ib_admin" + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Resource not found" + code: "NOT_FOUND" + details: {} + + Conflict: + description: Concurrent modification conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Resource was modified by another user" + code: "CONFLICT" + details: + current_version: 5 + + PreconditionFailed: + description: ETag mismatch - resource changed since last fetch + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Resource has been modified. Fetch the latest version and retry." + code: "PRECONDITION_FAILED" + details: + current_etag: '"abc123"' + + schemas: + Error: + type: object + required: [error, code] + properties: + error: + type: string + description: Human-readable error message + code: + type: string + description: Machine-readable error code + enum: + - BAD_REQUEST + - UNAUTHORIZED + - FORBIDDEN + - NOT_FOUND + - CONFLICT + - PRECONDITION_FAILED + - RATE_LIMIT_EXCEEDED + - INTERNAL_ERROR + - INVALID_INVITE + - INVITE_EXPIRED + - INVITE_ALREADY_USED + - EMAIL_MISMATCH + - MFA_REQUIRED + - INVALID_TOTP + - CONCURRENT_MODIFICATION + details: + type: object + additionalProperties: true + description: Additional error context + + AuthTokens: + type: object + properties: + access_token: + type: string + description: JWT access token (1 hour lifetime) + refresh_token: + type: string + description: Refresh token (7 day lifetime, rotates on use) + token_type: + type: string + enum: [Bearer] + expires_in: + type: integer + description: Access token lifetime in seconds + mfa_required: + type: boolean + description: Whether MFA verification is pending + user: + $ref: '#/components/schemas/User' + + User: + type: object + properties: + id: + type: string + format: uuid + email: + type: string + format: email + name: + type: string + organization_id: + type: string + format: uuid + organization_name: + type: string + mfa_enabled: + type: boolean + created_at: + type: string + format: date-time + + Project: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + example: "Project Falcon" + description: + type: string + stage: + type: string + enum: [pre_dataroom, dataroom, closed] + theme_id: + type: string + format: uuid + nullable: true + created_by: + type: string + format: uuid + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + stats: + type: object + properties: + workstreams: + type: integer + requests_total: + type: integer + requests_open: + type: integer + requests_completed: + type: integer + answers_published: + type: integer + my_role: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + description: Current user's role in this project + + ProjectCreate: + type: object + required: [name] + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + maxLength: 2000 + theme_id: + type: string + format: uuid + + ProjectUpdate: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + maxLength: 2000 + stage: + type: string + enum: [pre_dataroom, dataroom, closed] + theme_id: + type: string + format: uuid + nullable: true + + Workstream: + type: object + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + name: + type: string + example: "Finance" + slug: + type: string + example: "finance" + description: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + stats: + type: object + properties: + requests_total: + type: integer + requests_open: + type: integer + answers_published: + type: integer + + WorkstreamCreate: + type: object + required: [name] + properties: + name: + type: string + minLength: 1 + maxLength: 100 + slug: + type: string + pattern: '^[a-z0-9-]+$' + maxLength: 50 + description: URL-safe identifier (auto-generated from name if not provided) + description: + type: string + maxLength: 1000 + + WorkstreamUpdate: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 100 + description: + type: string + maxLength: 1000 + + RequestList: + type: object + properties: + id: + type: string + format: uuid + workstream_id: + type: string + format: uuid + name: + type: string + example: "Initial Due Diligence" + description: + type: string + created_by: + type: string + format: uuid + created_at: + type: string + format: date-time + request_count: + type: integer + + RequestListCreate: + type: object + required: [name] + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + maxLength: 1000 + + Request: + type: object + properties: + id: + type: string + format: uuid + workstream_id: + type: string + format: uuid + list_id: + type: string + format: uuid + nullable: true + ref: + type: string + example: "FIN-042" + description: Human-readable reference number + title: + type: string + example: "Provide audited financials FY2024" + body: + type: string + priority: + type: string + enum: [high, normal, low] + due_date: + type: string + format: date + nullable: true + status: + type: string + enum: [open, assigned, answered, vetted, published, closed] + stage: + type: string + enum: [pre_dataroom, dataroom, closed] + assignee_id: + type: string + format: uuid + nullable: true + description: Current assignee (who has it RIGHT NOW) + assignee: + $ref: '#/components/schemas/User' + return_to_id: + type: string + format: uuid + nullable: true + description: Who it returns to when completed + origin_id: + type: string + format: uuid + nullable: true + description: Ultimate requester who started the chain + created_by: + type: string + format: uuid + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + routing_chain: + type: array + description: Full routing history (visible to IB only) + items: + type: object + properties: + actor_id: + type: string + format: uuid + action: + type: string + enum: [created, forwarded, completed] + timestamp: + type: string + format: date-time + message: + type: string + linked_answers: + type: array + items: + type: object + properties: + answer_id: + type: string + format: uuid + confirmed: + type: boolean + linked_at: + type: string + format: date-time + + RequestCreate: + type: object + required: [title] + properties: + title: + type: string + minLength: 1 + maxLength: 500 + body: + type: string + maxLength: 10000 + list_id: + type: string + format: uuid + description: Optional request list to add to + priority: + type: string + enum: [high, normal, low] + default: normal + due_date: + type: string + format: date + assigned_to: + type: array + items: + type: string + format: uuid + description: Initial assignees + + RequestUpdate: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 500 + body: + type: string + maxLength: 10000 + priority: + type: string + enum: [high, normal, low] + due_date: + type: string + format: date + nullable: true + assigned_to: + type: array + items: + type: string + format: uuid + status: + type: string + enum: [open, assigned, answered, vetted, published, closed] + + Answer: + type: object + properties: + id: + type: string + format: uuid + workstream_id: + type: string + format: uuid + title: + type: string + body: + type: string + status: + type: string + enum: [draft, submitted, approved, rejected, published] + rejection_reason: + type: string + nullable: true + files: + type: array + items: + type: object + properties: + object_id: + type: string + pattern: '^[a-f0-9]{16}$' + filename: + type: string + size: + type: integer + mime_type: + type: string + linked_requests: + type: array + items: + type: object + properties: + request_id: + type: string + format: uuid + ref: + type: string + title: + type: string + confirmed: + type: boolean + broadcast_to: + type: string + enum: [linked_requesters, all_workstream, all_dataroom] + nullable: true + created_by: + type: string + format: uuid + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + submitted_at: + type: string + format: date-time + nullable: true + approved_at: + type: string + format: date-time + nullable: true + published_at: + type: string + format: date-time + nullable: true + + AnswerCreate: + type: object + required: [title] + properties: + title: + type: string + minLength: 1 + maxLength: 500 + body: + type: string + maxLength: 50000 + file_ids: + type: array + items: + type: string + pattern: '^[a-f0-9]{16}$' + description: Object IDs of uploaded files + request_ids: + type: array + items: + type: string + format: uuid + description: Requests this answer is linked to + + AnswerUpdate: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 500 + body: + type: string + maxLength: 50000 + file_ids: + type: array + items: + type: string + pattern: '^[a-f0-9]{16}$' + request_ids: + type: array + items: + type: string + format: uuid + + AnswerMatch: + type: object + properties: + answer_id: + type: string + format: uuid + answer_title: + type: string + answer_status: + type: string + enum: [published] + similarity_score: + type: number + format: float + minimum: 0 + maximum: 1 + description: Cosine similarity score (AI-computed) + matched_text: + type: string + description: Snippet of answer that matched the request + linked_requests: + type: integer + description: Number of other requests already linked to this answer + + Task: + type: object + description: A request in the user's personal inbox + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + project_name: + type: string + workstream_id: + type: string + format: uuid + workstream_name: + type: string + ref: + type: string + title: + type: string + priority: + type: string + enum: [high, normal, low] + due_date: + type: string + format: date + nullable: true + status: + type: string + enum: [open, assigned, answered, vetted, published, closed] + is_overdue: + type: boolean + return_to: + $ref: '#/components/schemas/User' + forwarded_by: + $ref: '#/components/schemas/User' + assigned_at: + type: string + format: date-time + created_at: + type: string + format: date-time + + AccessGrant: + type: object + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + workstream_id: + type: string + format: uuid + nullable: true + description: If null, access applies to all workstreams + user_id: + type: string + format: uuid + user: + $ref: '#/components/schemas/User' + role: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + ops: + type: string + description: "Operation flags: r=read, w=write, d=delete, m=manage" + example: "rw" + granted_by: + type: string + format: uuid + granted_at: + type: string + format: date-time + + AccessGrantCreate: + type: object + required: [user_id, role] + properties: + user_id: + type: string + format: uuid + role: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + workstream_id: + type: string + format: uuid + nullable: true + description: Scope to specific workstream (null = all workstreams) + ops: + type: string + default: "rw" + description: "Operation flags: r=read, w=write, d=delete, m=manage" + + Invite: + type: object + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + workstream_id: + type: string + format: uuid + nullable: true + email: + type: string + format: email + role: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + status: + type: string + enum: [pending, accepted, expired, revoked] + invited_by: + type: string + format: uuid + invited_by_name: + type: string + expires_at: + type: string + format: date-time + accepted_at: + type: string + format: date-time + nullable: true + created_at: + type: string + format: date-time + + InviteCreate: + type: object + required: [email, role] + properties: + email: + type: string + format: email + role: + type: string + enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer] + workstream_id: + type: string + format: uuid + nullable: true + description: Scope to specific workstream + message: + type: string + maxLength: 1000 + description: Personal message to include in invite email + + AuditEntry: + type: object + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + actor_id: + type: string + format: uuid + actor_email: + type: string + format: email + action: + type: string + description: | + Event type. Examples: + - auth.login, auth.logout, auth.mfa_enabled + - access.granted, access.revoked + - entry.created, entry.updated, entry.published + - file.uploaded, file.downloaded, file.deleted + - session.created, session.revoked + target_type: + type: string + enum: [entry, user, access, session, file] + nullable: true + target_id: + type: string + format: uuid + nullable: true + details: + type: object + additionalProperties: true + description: Action-specific details + ip_address: + type: string + user_agent: + type: string + timestamp: + type: string + format: date-time + hash: + type: string + description: SHA-256 hash for tamper detection (chain integrity) diff --git a/ONBOARDING-SPEC.md b/ONBOARDING-SPEC.md new file mode 100644 index 0000000..60f5f0b --- /dev/null +++ b/ONBOARDING-SPEC.md @@ -0,0 +1,1500 @@ +# Dealspace — Onboarding Flow Specification + +**Version:** 0.1 — 2026-02-28 +**Status:** Pre-implementation specification + +--- + +## 1. Account Creation (IB Admin) + +### 1.1 Registration Form + +**URL:** `https://app.muskepo.com/register` + +**Form Fields:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ Create Your Account │ +├─────────────────────────────────────────────────────────────┤ +│ Organization Name [Goldman Sachs ] │ +│ Your Name [Sarah Mitchell ] │ +│ Work Email [sarah.mitchell@gs.com ] │ +│ Password [•••••••••••••• ] │ +│ Confirm Password [•••••••••••••• ] │ +│ │ +│ □ I agree to the Terms of Service and Privacy Policy │ +│ │ +│ [ Create Account ] │ +│ │ +│ Already have an account? Sign in │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Validation:** +- Email must be corporate domain (no gmail.com, yahoo.com, etc.) — soft warning, not hard block +- Password: 12+ characters, at least one uppercase, one number, one special +- Organization name: required, 2–100 characters + +**Data Created:** +```sql +-- User record (pending verification) +INSERT INTO users (id, email, name, password_hash, org_name, status, created_at) +VALUES ('usr_abc123', 'sarah.mitchell@gs.com', 'Sarah Mitchell', '$argon2id$...', + 'Goldman Sachs', 'pending_verification', 1709136000000); +``` + +### 1.2 Email Verification Flow + +**Email sent immediately after form submission:** + +``` +Subject: Verify your Dealspace account + +Hi Sarah, + +Click below to verify your email and activate your Dealspace account: + +[Verify Email Address] +https://app.muskepo.com/verify?token=abc123def456 + +This link expires in 24 hours. + +If you didn't create this account, you can safely ignore this email. + +— +The Dealspace Team +``` + +**On click:** +1. Token validated (single-use, 24h expiry) +2. User status → `pending_mfa` +3. Redirect to `/setup/mfa` + +### 1.3 MFA Setup (Mandatory for IB Admin) + +**URL:** `https://app.muskepo.com/setup/mfa` + +**Step-by-step flow:** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Set Up Two-Factor Authentication │ +│ │ +│ Step 1 of 3: Install Authenticator App │ +│ ───────────────────────────────────────────────────────── │ +│ │ +│ Download one of these apps on your phone: │ +│ │ +│ • Google Authenticator (iOS / Android) │ +│ • Microsoft Authenticator (iOS / Android) │ +│ • 1Password, Authy, or any TOTP app │ +│ │ +│ Already have one? [Continue →] │ +└─────────────────────────────────────────────────────────────┘ +``` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Set Up Two-Factor Authentication │ +│ │ +│ Step 2 of 3: Scan QR Code │ +│ ───────────────────────────────────────────────────────── │ +│ │ +│ ┌───────────────┐ │ +│ │ [QR CODE] │ │ +│ │ │ │ +│ └───────────────┘ │ +│ │ +│ Can't scan? Enter this code manually: │ +│ JBSWY3DPEHPK3PXP (tap to copy) │ +│ │ +│ [Continue →] │ +└─────────────────────────────────────────────────────────────┘ +``` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Set Up Two-Factor Authentication │ +│ │ +│ Step 3 of 3: Verify Setup │ +│ ───────────────────────────────────────────────────────── │ +│ │ +│ Enter the 6-digit code from your authenticator app: │ +│ │ +│ [ 4 ] [ 7 ] [ 2 ] [ 9 ] [ 1 ] [ 5 ] │ +│ │ +│ [ Verify & Complete Setup ] │ +└─────────────────────────────────────────────────────────────┘ +``` + +**On successful verification:** +1. TOTP secret stored (encrypted) in user record +2. Recovery codes generated (10 codes, 8 characters each) +3. User status → `active` +4. Show recovery codes screen: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Save Your Recovery Codes │ +│ │ +│ Store these in a safe place. You'll need them if you │ +│ lose access to your authenticator app. │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 1. XKCD-7843 6. BEEF-2947 │ │ +│ │ 2. PASS-9126 7. DORK-5182 │ │ +│ │ 3. MOON-4521 8. JAZZ-7394 │ │ +│ │ 4. TREE-8734 9. FORK-6215 │ │ +│ │ 5. WAVE-3069 10. LAMP-9847 │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ [Download as PDF] [Copy to clipboard] │ +│ │ +│ □ I have saved my recovery codes │ +│ │ +│ [ Continue to Dealspace ] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 1.4 First Login Experience + +**URL:** `https://app.muskepo.com/app` + +User lands on an empty dashboard with a prominent call to action: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE Goldman Sachs | Sarah Mitchell ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ │ +│ ┌──────────────────────────┐ │ +│ │ 📁 │ │ +│ │ │ │ +│ │ No projects yet │ │ +│ │ │ │ +│ │ Create your first │ │ +│ │ deal to get started │ │ +│ │ │ │ +│ │ [+ Create Project] │ │ +│ │ │ │ +│ └──────────────────────────┘ │ +│ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. First Project Setup Wizard + +Clicking "Create Project" launches a 5-step wizard. Each step is a focused screen. Progress bar at top. + +### 2.1 Step 1: Project Name + Deal Type + +**URL:** `https://app.muskepo.com/app/projects/new/basics` + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Project Step 1 of 5 ━━━○○○○ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Project Name │ +│ [TechCorp Acquisition - Project Phoenix ] │ +│ │ +│ Deal Type │ +│ ┌───────────────────┐ ┌───────────────────┐ │ +│ │ ○ Sell-side │ │ ○ Buy-side │ │ +│ │ M&A Advisory │ │ M&A Advisory │ │ +│ └───────────────────┘ └───────────────────┘ │ +│ ┌───────────────────┐ ┌───────────────────┐ │ +│ │ ○ Merger │ │ ○ Other │ │ +│ │ │ │ │ │ +│ └───────────────────┘ └───────────────────┘ │ +│ │ +│ Code Name (optional, shown to buyers instead of project name) │ +│ [Project Phoenix ] │ +│ │ +│ [Cancel] [Next: Workstreams] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +**Data Created (in memory, not persisted until Step 5):** +```json +{ + "name": "TechCorp Acquisition - Project Phoenix", + "deal_type": "sell_side", + "code_name": "Project Phoenix", + "workstreams": [], + "team": [], + "seller_contacts": [] +} +``` + +### 2.2 Step 2: Configure Workstreams + +**URL:** `https://app.muskepo.com/app/projects/new/workstreams` + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Project Step 2 of 5 ━━━━○○○ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Configure Workstreams │ +│ │ +│ These are the areas your deal will cover. You can add, remove, or │ +│ rename them. Each workstream has its own access controls. │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ ☑ Finance [Edit name] [Remove] │ │ +│ │ ☑ Legal [Edit name] [Remove] │ │ +│ │ ☑ IT [Edit name] [Remove] │ │ +│ │ ☑ HR [Edit name] [Remove] │ │ +│ │ ☑ Operations [Edit name] [Remove] │ │ +│ │ ☐ Tax [Edit name] [Remove] │ │ +│ │ ☐ Commercial [Edit name] [Remove] │ │ +│ │ ☐ Environmental [Edit name] [Remove] │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ [+ Add Custom Workstream] │ +│ │ +│ [Back] [Next: Invite Team] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +**Default workstreams (pre-checked):** +- Finance +- Legal +- IT +- HR +- Operations + +**Additional suggestions (unchecked):** +- Tax +- Commercial +- Environmental +- Regulatory +- Insurance + +**Custom workstream modal:** +``` +┌────────────────────────────────────┐ +│ Add Workstream │ +│ │ +│ Name: [IP & Patents ] │ +│ │ +│ [Cancel] [Add Workstream] │ +└────────────────────────────────────┘ +``` + +### 2.3 Step 3: Invite IB Team Members + +**URL:** `https://app.muskepo.com/app/projects/new/team` + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Project Step 3 of 5 ━━━━━○○ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Invite Your Team │ +│ Add team members and assign them to workstreams. │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Email Name Role Workstreams │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ mike@gs.com Mike Chen ib_member Finance, Tax │ │ +│ │ [Edit] [Remove] │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ jen@gs.com Jen Park ib_member Legal │ │ +│ │ [Edit] [Remove] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ [+ Add Team Member] │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ Or paste multiple emails (one per line): │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ alex@gs.com │ │ +│ │ david@gs.com │ │ +│ │ lisa@gs.com │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ [Parse & Add] │ +│ │ +│ [Back] [Next: Invite Seller] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +**Add team member modal:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ Add Team Member │ +│ │ +│ Email Address │ +│ [mike.chen@gs.com ] │ +│ │ +│ Full Name │ +│ [Mike Chen ] │ +│ │ +│ Role │ +│ ○ IB Admin (full access, can manage project) │ +│ ● IB Member (manage requests + vet answers in workstreams) │ +│ │ +│ Workstream Access │ +│ ☑ Finance │ +│ ☐ Legal │ +│ ☐ IT │ +│ ☐ HR │ +│ ☐ Operations │ +│ ☑ Tax │ +│ │ +│ [Cancel] [Add to Team] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.4 Step 4: Invite Seller Contacts + +**URL:** `https://app.muskepo.com/app/projects/new/seller` + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Project Step 4 of 5 ━━━━━━○ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Invite Seller Team │ +│ Add the sell-side company's contacts. They'll receive access invites. │ +│ │ +│ Seller Organization Name │ +│ [TechCorp Inc. ] │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Email Name Title Role │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ john.smith@techcorp. John Smith CFO seller_ │ │ +│ │ com admin │ │ +│ │ [Edit] [Remove] │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ mary.johnson@techcorp Mary Johnson Controller seller_ │ │ +│ │ .com member │ │ +│ │ [Edit] [Remove] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ [+ Add Seller Contact] │ +│ │ +│ ⓘ Seller Admin can manage their team and see all workstreams. │ +│ ⓘ Seller Members only see assigned workstreams. │ +│ │ +│ [Back] [Next: Review & Launch] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +**Add seller contact modal:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ Add Seller Contact │ +│ │ +│ Email Address │ +│ [john.smith@techcorp.com ] │ +│ │ +│ Full Name │ +│ [John Smith ] │ +│ │ +│ Title │ +│ [CFO ] │ +│ │ +│ Role │ +│ ● Seller Admin (manage seller team, all workstreams) │ +│ ○ Seller Member (answer requests in assigned workstreams) │ +│ │ +│ Workstream Access (for Seller Member only) │ +│ ☑ Finance │ +│ ☐ Legal │ +│ ... │ +│ │ +│ [Cancel] [Add Contact] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.5 Step 5: Review & Launch + +**URL:** `https://app.muskepo.com/app/projects/new/review` + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Project Step 5 of 5 ━━━━━━━ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Review & Launch │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ PROJECT DETAILS │ │ +│ │ Name: TechCorp Acquisition - Project Phoenix │ │ +│ │ Code Name: Project Phoenix (shown to buyers) │ │ +│ │ Type: Sell-side M&A Advisory [Edit] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ WORKSTREAMS (5) │ │ +│ │ Finance · Legal · IT · HR · Operations [Edit] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ YOUR TEAM (3 invites will be sent) │ │ +│ │ • Mike Chen (mike@gs.com) — IB Member, Finance + Tax │ │ +│ │ • Jen Park (jen@gs.com) — IB Member, Legal │ │ +│ │ • Alex Wong (alex@gs.com) — IB Member, IT [Edit] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ SELLER TEAM (2 invites will be sent) │ │ +│ │ • John Smith (john.smith@techcorp.com) — Seller Admin │ │ +│ │ • Mary Johnson (mary.johnson@techcorp.com) — Seller Member │ │ +│ │ [Edit] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ⓘ 5 invitation emails will be sent when you launch. │ +│ │ +│ [Back] [Launch Project →] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.6 Data Created on Launch + +**Database operations (single transaction):** + +```sql +-- Project entry +INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, summary, data, stage) +VALUES ('prj_abc123', 'prj_abc123', '', 'project', 0, 'techcorp-acquisition', + 'TechCorp Acquisition - Project Phoenix', + PACK('{"name":"...","code_name":"...","deal_type":"sell_side","seller_org":"TechCorp Inc."}'), + 'pre_dataroom'); + +-- Workstream entries (5x) +INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, summary, data, stage) +VALUES + ('ws_fin', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'finance', 'Finance', PACK('{}'), 'pre_dataroom'), + ('ws_leg', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'legal', 'Legal', PACK('{}'), 'pre_dataroom'), + ('ws_it', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'it', 'IT', PACK('{}'), 'pre_dataroom'), + ('ws_hr', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'hr', 'HR', PACK('{}'), 'pre_dataroom'), + ('ws_ops', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'operations', 'Operations', PACK('{}'), 'pre_dataroom'); + +-- Access records (for creating IB admin) +INSERT INTO access (id, project_id, workstream_id, user_id, role, ops, granted_by, granted_at) +VALUES ('acc_001', 'prj_abc123', NULL, 'usr_sarah', 'ib_admin', 'rwdm', 'usr_sarah', 1709136000000); + +-- Pending invitations (stored until accepted) +INSERT INTO invitations (id, project_id, email, name, role, workstream_ids, invited_by, expires_at) +VALUES + ('inv_001', 'prj_abc123', 'mike@gs.com', 'Mike Chen', 'ib_member', '["ws_fin"]', 'usr_sarah', ...), + ('inv_002', 'prj_abc123', 'jen@gs.com', 'Jen Park', 'ib_member', '["ws_leg"]', 'usr_sarah', ...), + ('inv_003', 'prj_abc123', 'john.smith@techcorp.com', 'John Smith', 'seller_admin', NULL, 'usr_sarah', ...), + ('inv_004', 'prj_abc123', 'mary.johnson@techcorp.com', 'Mary Johnson', 'seller_member', '["ws_fin"]', 'usr_sarah', ...); +``` + +### 2.7 Emails Sent on Launch + +**To IB team members (3x):** +See [Section 6.1 — IB Team Invite Email](#61-ib-team-invite-email) + +**To Seller contacts (2x):** +See [Section 6.2 — Seller Invite Email](#62-seller-invite-email) + +### 2.8 Post-Launch Screen + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [TechCorp Acquisition ▼] Goldman Sachs | Sarah ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ Finance | Legal | IT | HR | Operations │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ✓ Project Created │ +│ │ +│ 5 invitations sent. Here's what happens next: │ +│ │ +│ 1. Your team accepts their invitations │ +│ 2. Seller contacts accept and set up their accounts │ +│ 3. Create your first request list to start the due diligence │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Ready to start? │ │ +│ │ │ │ +│ │ [+ Create Request List] [View Team Status] │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. Invite Flow (Seller Side) + +### 3.1 Seller Invite Email + +**The actual email the Seller CFO receives:** + +``` +From: TechCorp Acquisition via Dealspace +Reply-To: sarah.mitchell@gs.com +To: john.smith@techcorp.com +Subject: You've been invited to Project Phoenix + +─────────────────────────────────────────────────────────────────────── + +Hi John, + +Sarah Mitchell from Goldman Sachs has invited you to join the +due diligence process for Project Phoenix. + +You've been assigned as Seller Admin, which means you can: +• See all requests from the deal team +• Assign tasks to your colleagues +• Manage your team's access and responses + +[Accept Invitation] +https://app.muskepo.com/invite/abc123def456 + +This invitation expires in 7 days. If you have questions, reply +to this email to reach Sarah directly. + +─────────────────────────────────────────────────────────────────────── + +What is Dealspace? +Dealspace is a secure platform for managing M&A due diligence. +All documents are encrypted and watermarked. You control what +gets shared and when. + +Questions? Contact support@muskepo.com +``` + +### 3.2 Invitation Landing Page + +**URL:** `https://app.muskepo.com/invite/abc123def456` + +**For new users (no existing account):** + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ You're invited to │ │ +│ │ PROJECT PHOENIX │ │ +│ │ │ │ +│ │ Goldman Sachs has invited you to participate in this deal. │ │ +│ │ │ │ +│ │ Your Role: Seller Admin │ │ +│ │ Organization: TechCorp Inc. │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ Create Your Account │ +│ │ +│ Email (pre-filled, read-only) │ +│ [john.smith@techcorp.com ] │ +│ │ +│ Your Name │ +│ [John Smith ] │ +│ │ +│ Create Password │ +│ [•••••••••••• ] │ +│ 12+ characters, including uppercase, number, and special character │ +│ │ +│ Confirm Password │ +│ [•••••••••••• ] │ +│ │ +│ □ I agree to the Terms of Service and Privacy Policy │ +│ │ +│ [ Create Account & Join Project ] │ +│ │ +│ Already have an account? Sign in │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 3.3 MFA Setup (Seller) + +**For seller_admin: strongly recommended (shown after account creation)** +**For seller_member: optional (can skip)** + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ Secure Your Account │ +│ │ +│ Two-factor authentication adds an extra layer of security. │ +│ Goldman Sachs recommends enabling this for all deal participants. │ +│ │ +│ [Set Up Two-Factor Authentication] │ +│ │ +│ [Skip for now — I'll do this later] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 3.4 First Login: Seller Task Inbox + +**URL:** `https://app.muskepo.com/app` (after accepting invite) + +On first login, the Seller Admin sees their task inbox — which is likely empty until the IB team creates request lists. + +**Empty state (no requests yet):** + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [Project Phoenix ▼] TechCorp | John ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MY TASKS [All Workstreams ▼] │ +│ │ +│ ┌──────────────────────────┐ │ +│ │ ✓ │ │ +│ │ │ │ +│ │ No tasks yet │ │ +│ │ │ │ +│ │ Goldman Sachs hasn't │ │ +│ │ sent any requests yet. │ │ +│ │ We'll notify you when │ │ +│ │ they do. │ │ +│ │ │ │ +│ └──────────────────────────┘ │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ While you wait, you can: │ +│ • [Invite your team members] │ +│ • [Set up your profile] │ +│ • [Review the quick start guide] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +**When requests exist (typical first login):** + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [Project Phoenix ▼] TechCorp | John ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MY TASKS [Filter ▼] [Sort: Due Date ▼] 🔍 │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ You have 12 new requests │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ FINANCE (8) │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ ⚡ FIN-001 Audited financial statements FY2023-2024 │ │ +│ │ Due: Mar 15 • High Priority • Assigned to: You [→] │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ FIN-002 Revenue breakdown by product line │ │ +│ │ Due: Mar 18 • Normal • Assigned to: You [→] │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ FIN-003 Accounts receivable aging report │ │ +│ │ Due: Mar 18 • Normal • Assigned to: Mary Johnson [→] │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ [Show 5 more...] │ +│ │ +│ LEGAL (4) │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ LEG-001 Material contracts list │ │ +│ │ Due: Mar 20 • High Priority • Assigned to: You [→] │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ [Show 3 more...] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. First Request List + +### 4.1 Creating a Request List + +**IB member navigates to a workstream and clicks "New Request List"** + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [TechCorp Acquisition ▼] Goldman Sachs | Mike ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ Finance | Legal | IT | HR | Operations │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ FINANCE — Request Lists │ +│ │ +│ [+ New Request List] [Import from Excel] │ +│ │ +│ (No request lists yet) │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 New Request List Modal + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Request List │ +│ │ +│ List Name │ +│ [Initial Finance DD Requests ] │ +│ │ +│ Description (optional) │ +│ [First round of financial due diligence requests for Project Phoenix ] │ +│ │ +│ Default Due Date │ +│ [2026-03-15 ] (can be overridden per request) │ +│ │ +│ Default Assignee │ +│ [John Smith (Seller Admin) ▼ ] (can be overridden per request) │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ │ +│ How would you like to add requests? │ +│ │ +│ ○ Add requests manually (one at a time) │ +│ ● Import from Excel template │ +│ [Download Template] │ +│ │ +│ [Cancel] [Create List] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.3 Excel Import Template + +**File: `dealspace-request-template.xlsx`** + +| Column | Header | Required | Description | Valid Values | +|--------|--------|----------|-------------|--------------| +| A | ref | No | Reference number (auto-generated if blank) | e.g., FIN-001 | +| B | title | Yes | Request title | Max 200 chars | +| C | description | No | Detailed description | Max 5000 chars | +| D | priority | No | Priority level | high, normal, low (default: normal) | +| E | due_date | No | Due date | YYYY-MM-DD (default: list default) | +| F | assigned_to | No | Assignee email | Must be valid seller email | + +**Sample template content:** + +``` +ref title description priority due_date assigned_to +FIN-001 Audited financial statements FY2023-2024 Complete audited financials for the last two fiscal... high 2026-03-15 john.smith@techcorp.com +FIN-002 Revenue breakdown by product line Monthly revenue by product line for last 24 months normal 2026-03-18 john.smith@techcorp.com +FIN-003 Accounts receivable aging report Current AR aging as of latest month-end normal 2026-03-18 mary.johnson@techcorp.com +FIN-004 Accounts payable aging report Current AP aging as of latest month-end normal 2026-03-18 mary.johnson@techcorp.com +FIN-005 Cash flow projections FY2025-2027 3-year projected cash flows high 2026-03-20 john.smith@techcorp.com +FIN-006 Bank statements (all accounts) Last 12 months of bank statements for all accounts normal 2026-03-22 +FIN-007 Debt schedule Current debt obligations with terms and covenants high 2026-03-18 john.smith@techcorp.com +FIN-008 Capital expenditure history CapEx by category for last 3 years normal 2026-03-25 mary.johnson@techcorp.com +``` + +### 4.4 Import Preview Screen + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Import Requests — Preview │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ✓ 8 requests parsed successfully │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Ref Title Priority Due To │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ FIN-001 Audited financial statements... ⚡ High Mar 15 JS │ │ +│ │ FIN-002 Revenue breakdown by product... — Mar 18 JS │ │ +│ │ FIN-003 Accounts receivable aging... — Mar 18 MJ │ │ +│ │ FIN-004 Accounts payable aging... — Mar 18 MJ │ │ +│ │ FIN-005 Cash flow projections... ⚡ High Mar 20 JS │ │ +│ │ FIN-006 Bank statements (all accounts) — Mar 22 — │ │ +│ │ FIN-007 Debt schedule ⚡ High Mar 18 JS │ │ +│ │ FIN-008 Capital expenditure history — Mar 25 MJ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ⚠ 1 request has no assignee (FIN-006) — will be assigned to John Smith │ +│ │ +│ □ Send notification emails to assignees now │ +│ □ Send a single summary email per assignee (instead of per-request) │ +│ │ +│ [Cancel] [Back to Edit] [Import 8 Requests →] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.5 Notifications Fired + +**On import with "Send notification emails" checked:** + +1. **John Smith receives:** "You have 6 new requests in Project Phoenix" +2. **Mary Johnson receives:** "You have 2 new requests in Project Phoenix" + +See [Section 6.3 — New Tasks Notification](#63-new-tasks-notification) for email copy. + +--- + +## 5. The "Aha Moment" + +### 5.1 For the Investment Bank + +**The moment:** When the first answer comes back with documents attached, automatically indexed and searchable, and they click "Approve" to publish it to the data room — all in 3 clicks. + +**The comparison:** In the old workflow, this would involve: +1. Receiving email with attachment +2. Downloading attachment +3. Opening VDR +4. Navigating to correct folder +5. Uploading document +6. Renaming to match convention +7. Setting permissions +8. Updating tracking spreadsheet +9. Emailing confirmation + +**Design toward this:** +- Answer review screen shows document preview inline +- One-click approve → "Published to Data Room" confirmation +- Activity feed shows real-time: "FIN-001 answered → vetted → published" +- Buyer count badge: "3 buyers can now see this" + +### 5.2 For the Seller + +**The moment:** When they upload their first answer and immediately see exactly what the bank is waiting on — a clear task list with priorities, due dates, and progress tracking, instead of hunting through email threads. + +**The comparison:** In the old workflow: +1. Receive request list via email (usually PDF or Word) +2. Print it out / copy to spreadsheet +3. Manually track who's doing what +4. Hunt through email for clarifications +5. Reply-all with attachments +6. No visibility into whether IB received/reviewed + +**Design toward this:** +- Clear task inbox with filters (by workstream, priority, due date, assignee) +- Progress bar per workstream: "Finance: 3/12 complete" +- Real-time status: "Submitted → Under Review → Approved ✓" +- Comments thread inline — no email back-and-forth +- Mobile-friendly for on-the-go approvals + +### 5.3 For the Buyer + +**The moment:** When they submit a question and the AI matches it to an existing answer instantly — no waiting, no IB intermediary, just immediate access to relevant documents. + +**Design toward this:** +- Search bar prominent in data room +- "Ask a question" that triggers AI matching +- Instant match: "This might answer your question" with preview +- If no match: "Your question has been submitted" with expected response time +- Broadcast notification: "New answer matches your previous question" + +--- + +## 6. Email Templates + +### 6.1 IB Team Invite Email + +``` +From: Dealspace +Reply-To: sarah.mitchell@gs.com +To: mike.chen@gs.com +Subject: Join Project Phoenix on Dealspace + +─────────────────────────────────────────────────────────────────────── + +Hi Mike, + +Sarah Mitchell has invited you to join the deal team for +Project Phoenix on Dealspace. + +Your Role: IB Member +Workstreams: Finance, Tax + +[Accept Invitation] +https://app.muskepo.com/invite/xyz789 + +This invitation expires in 7 days. + +─────────────────────────────────────────────────────────────────────── + +Dealspace is Goldman Sachs' secure platform for managing M&A +due diligence. If you have questions about this invitation, +contact Sarah directly. +``` + +### 6.2 Seller Invite Email + +``` +From: Project Phoenix via Dealspace +Reply-To: sarah.mitchell@gs.com +To: john.smith@techcorp.com +Subject: You've been invited to Project Phoenix + +─────────────────────────────────────────────────────────────────────── + +Hi John, + +Sarah Mitchell from Goldman Sachs has invited you to join the +due diligence process for Project Phoenix. + +You've been assigned as Seller Admin, which means you can: +• View and respond to all due diligence requests +• Assign tasks to your colleagues +• Track your team's progress across all workstreams + +[Accept Invitation] +https://app.muskepo.com/invite/abc123 + +This invitation expires in 7 days. If you have questions, +reply to this email to reach Sarah directly. + +─────────────────────────────────────────────────────────────────────── + +What is Dealspace? + +Dealspace is a secure platform for managing M&A due diligence. +All documents are encrypted and dynamically watermarked. You +maintain full control over what gets shared and when. + +Need help? Contact support@muskepo.com +``` + +### 6.3 New Tasks Notification + +``` +From: Project Phoenix via Dealspace +To: john.smith@techcorp.com +Subject: You have 6 new requests in Project Phoenix + +─────────────────────────────────────────────────────────────────────── + +Hi John, + +Goldman Sachs has sent you 6 new requests for Project Phoenix. + +HIGH PRIORITY (3): +• FIN-001: Audited financial statements FY2023-2024 — Due Mar 15 +• FIN-005: Cash flow projections FY2025-2027 — Due Mar 20 +• FIN-007: Debt schedule — Due Mar 18 + +NORMAL PRIORITY (3): +• FIN-002: Revenue breakdown by product line — Due Mar 18 +• FIN-006: Bank statements (all accounts) — Due Mar 22 +• Plus 1 more + +[View Your Tasks] +https://app.muskepo.com/app/tasks + +─────────────────────────────────────────────────────────────────────── + +You're receiving this because you're assigned to requests in +Project Phoenix. Manage notification preferences in your settings. +``` + +### 6.4 Answer Submitted — Needs Vetting + +``` +From: Project Phoenix via Dealspace +To: mike.chen@gs.com +Subject: [Action Required] Answer submitted: FIN-001 Audited financials + +─────────────────────────────────────────────────────────────────────── + +Hi Mike, + +John Smith from TechCorp has submitted an answer that needs +your review. + +REQUEST: FIN-001 — Audited financial statements FY2023-2024 +SUBMITTED: 2 files attached +• TechCorp_Audited_Financials_2023.pdf (2.4 MB) +• TechCorp_Audited_Financials_2024.pdf (2.8 MB) + +COMMENT FROM SELLER: +"Attached are the audited financials for FY2023 and FY2024. +The 2024 audit was completed last month. Let me know if you +need the management letters as well." + +[Review & Approve] +https://app.muskepo.com/app/review/ans_xyz123 + +─────────────────────────────────────────────────────────────────────── + +Once approved, this answer will be published to the data room +and visible to all authorized buyers. +``` + +### 6.5 Answer Approved / Rejected (Seller Notification) + +**Approved:** +``` +From: Project Phoenix via Dealspace +To: john.smith@techcorp.com +Subject: ✓ Answer approved: FIN-001 Audited financials + +─────────────────────────────────────────────────────────────────────── + +Hi John, + +Your answer to FIN-001 has been approved and published to +the data room. + +REQUEST: FIN-001 — Audited financial statements FY2023-2024 +STATUS: ✓ Published to Data Room +APPROVED BY: Mike Chen (Goldman Sachs) + +[View in Data Room] +https://app.muskepo.com/app/dataroom/ans_xyz123 + +─────────────────────────────────────────────────────────────────────── +``` + +**Rejected:** +``` +From: Project Phoenix via Dealspace +To: john.smith@techcorp.com +Subject: ⚠ Revision requested: FIN-001 Audited financials + +─────────────────────────────────────────────────────────────────────── + +Hi John, + +Your answer to FIN-001 requires revision before it can be +published. + +REQUEST: FIN-001 — Audited financial statements FY2023-2024 +STATUS: Revision Requested + +FEEDBACK FROM MIKE CHEN: +"The 2024 financials are draft — we need the final audited +version with the auditor's opinion letter. Please also include +the management representation letters for both years." + +[Update Your Answer] +https://app.muskepo.com/app/tasks/req_001 + +─────────────────────────────────────────────────────────────────────── + +Reply to this request directly in Dealspace to keep the +conversation in one place. +``` + +### 6.6 New Answer Available in Data Room (Buyer Notification) + +``` +From: Project Phoenix via Dealspace +To: analyst@buyside-pe.com +Subject: New document available: Audited Financials + +─────────────────────────────────────────────────────────────────────── + +A new document matching your interests is now available in the +Project Phoenix data room. + +DOCUMENT: Audited Financial Statements FY2023-2024 +CATEGORY: Finance +ADDED: February 28, 2026 + +This document may be relevant to your previous question about +"historical financial performance." + +[View in Data Room] +https://app.muskepo.com/app/dataroom/ans_xyz123 + +─────────────────────────────────────────────────────────────────────── + +You're receiving this because you requested information about +financial statements. Manage notification preferences in settings. +``` + +--- + +## 7. Empty States + +### 7.1 IB Admin — No Projects + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE Goldman Sachs | Sarah Mitchell ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ │ +│ ┌──────────────────────────┐ │ +│ │ 📁 │ │ +│ │ │ │ +│ │ No projects yet │ │ +│ │ │ │ +│ │ Dealspace helps you │ │ +│ │ run M&A due diligence │ │ +│ │ 10x faster. │ │ +│ │ │ │ +│ │ [+ Create Your First │ │ +│ │ Project] │ │ +│ │ │ │ +│ └──────────────────────────┘ │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ │ +│ Or explore with sample data: │ +│ [Load Demo Project] — See how a typical deal flows │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 7.2 Seller Member — Empty Task Inbox + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [Project Phoenix ▼] TechCorp | Mary ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ MY TASKS │ +│ │ +│ ┌──────────────────────────┐ │ +│ │ ✓ │ │ +│ │ │ │ +│ │ You're all caught up! │ │ +│ │ │ │ +│ │ No tasks assigned to │ │ +│ │ you right now. │ │ +│ │ │ │ +│ └──────────────────────────┘ │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ │ +│ Your completed tasks (3): │ +│ • FIN-003: Accounts receivable aging — Approved ✓ │ +│ • FIN-004: Accounts payable aging — Approved ✓ │ +│ • FIN-008: Capital expenditure history — Under Review │ +│ │ +│ [View Completed Tasks] │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 7.3 Buyer — Empty Data Room + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEALSPACE [Project Phoenix ▼] Blackstone | James ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ Data Room | My Questions │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ DATA ROOM 🔍 Search documents... │ +│ │ +│ ┌──────────────────────────┐ │ +│ │ 📂 │ │ +│ │ │ │ +│ │ Data room is being │ │ +│ │ prepared │ │ +│ │ │ │ +│ │ The sell-side team is │ │ +│ │ gathering documents. │ │ +│ │ We'll notify you when │ │ +│ │ materials are ready. │ │ +│ │ │ │ +│ └──────────────────────────┘ │ +│ │ +│ ────────────────────────────────────────────────────────────────────── │ +│ │ +│ In the meantime: │ +│ [Submit Questions] — Tell us what you're looking for │ +│ [View Deal Summary] — Overview of Project Phoenix │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 8. Demo Data + +### 8.1 Seed Script Concept + +```go +// cmd/seed/main.go +// Run: go run ./cmd/seed --demo + +package main + +func seedDemoProject(db *sql.DB) { + // 1. Create organizations + gsOrg := createOrg("Goldman Sachs", "gs.com") + techCorpOrg := createOrg("TechCorp Inc.", "techcorp.com") + blackstoneOrg := createOrg("Blackstone", "blackstone.com") + baincapOrg := createOrg("Bain Capital", "baincapital.com") + + // 2. Create users + // Goldman Sachs (IB) + sarahIB := createUser("sarah.mitchell@gs.com", "Sarah Mitchell", gsOrg, "ib_admin") + mikeIB := createUser("mike.chen@gs.com", "Mike Chen", gsOrg, "ib_member") + jenIB := createUser("jen.park@gs.com", "Jen Park", gsOrg, "ib_member") + alexIB := createUser("alex.wong@gs.com", "Alex Wong", gsOrg, "ib_member") + + // TechCorp (Seller) + johnSeller := createUser("john.smith@techcorp.com", "John Smith", techCorpOrg, "seller_admin") + marySeller := createUser("mary.johnson@techcorp.com", "Mary Johnson", techCorpOrg, "seller_member") + tomSeller := createUser("tom.wilson@techcorp.com", "Tom Wilson", techCorpOrg, "seller_member") + lisaSeller := createUser("lisa.chen@techcorp.com", "Lisa Chen", techCorpOrg, "seller_member") + + // Buyers + jamesBuyer := createUser("james.lee@blackstone.com", "James Lee", blackstoneOrg, "buyer_admin") + emilaBuyer := createUser("emily.davis@baincapital.com", "Emily Davis", baincapOrg, "buyer_admin") + + // 3. Create project + project := createProject(ProjectInput{ + Name: "TechCorp Acquisition - Project Phoenix", + CodeName: "Project Phoenix", + DealType: "sell_side", + SellerOrg: techCorpOrg, + CreatedBy: sarahIB, + }) + + // 4. Create workstreams + financeWS := createWorkstream(project, "Finance", "finance") + legalWS := createWorkstream(project, "Legal", "legal") + itWS := createWorkstream(project, "IT", "it") + + // 5. Assign access + // IB team + grantAccess(sarahIB, project, nil, "ib_admin") + grantAccess(mikeIB, project, financeWS, "ib_member") + grantAccess(jenIB, project, legalWS, "ib_member") + grantAccess(alexIB, project, itWS, "ib_member") + + // Seller team + grantAccess(johnSeller, project, nil, "seller_admin") + grantAccess(marySeller, project, financeWS, "seller_member") + grantAccess(tomSeller, project, legalWS, "seller_member") + grantAccess(lisaSeller, project, itWS, "seller_member") + + // Buyers (data room only) + grantAccess(jamesBuyer, project, nil, "buyer_admin") + grantAccess(emilaBuyer, project, nil, "buyer_admin") + + // 6. Create Finance request list + financeRL := createRequestList(RequestListInput{ + Name: "Initial Finance DD", + Workstream: financeWS, + CreatedBy: mikeIB, + DefaultDue: time.Now().Add(14 * 24 * time.Hour), + }) + + // Finance requests (mix of statuses for realism) + createRequest(financeRL, RequestInput{ + Ref: "FIN-001", + Title: "Audited financial statements FY2023-2024", + Priority: "high", + AssignedTo: johnSeller, + Status: "published", // Already in data room + }) + createRequest(financeRL, RequestInput{ + Ref: "FIN-002", + Title: "Revenue breakdown by product line", + Priority: "normal", + AssignedTo: marySeller, + Status: "vetted", // Approved, pending publish + }) + createRequest(financeRL, RequestInput{ + Ref: "FIN-003", + Title: "Accounts receivable aging report", + Priority: "normal", + AssignedTo: marySeller, + Status: "submitted", // Awaiting IB review + }) + createRequest(financeRL, RequestInput{ + Ref: "FIN-004", + Title: "Accounts payable aging report", + Priority: "normal", + AssignedTo: marySeller, + Status: "assigned", // Seller working on it + }) + createRequest(financeRL, RequestInput{ + Ref: "FIN-005", + Title: "Cash flow projections FY2025-2027", + Priority: "high", + AssignedTo: johnSeller, + Status: "open", // Not started + }) + + // 7. Create Legal request list + legalRL := createRequestList(RequestListInput{ + Name: "Legal DD - Contracts & IP", + Workstream: legalWS, + CreatedBy: jenIB, + }) + + createRequest(legalRL, RequestInput{ + Ref: "LEG-001", + Title: "Material contracts list", + Priority: "high", + AssignedTo: tomSeller, + Status: "submitted", + }) + createRequest(legalRL, RequestInput{ + Ref: "LEG-002", + Title: "Customer contracts (top 10 by revenue)", + Priority: "high", + AssignedTo: tomSeller, + Status: "assigned", + }) + createRequest(legalRL, RequestInput{ + Ref: "LEG-003", + Title: "IP portfolio summary", + Priority: "normal", + AssignedTo: tomSeller, + Status: "open", + }) + createRequest(legalRL, RequestInput{ + Ref: "LEG-004", + Title: "Pending litigation summary", + Priority: "high", + AssignedTo: tomSeller, + Status: "open", + }) + + // 8. Create IT request list + itRL := createRequestList(RequestListInput{ + Name: "IT Infrastructure & Security", + Workstream: itWS, + CreatedBy: alexIB, + }) + + createRequest(itRL, RequestInput{ + Ref: "IT-001", + Title: "System architecture diagram", + Priority: "high", + AssignedTo: lisaSeller, + Status: "published", + }) + createRequest(itRL, RequestInput{ + Ref: "IT-002", + Title: "Cloud infrastructure costs", + Priority: "normal", + AssignedTo: lisaSeller, + Status: "submitted", + }) + createRequest(itRL, RequestInput{ + Ref: "IT-003", + Title: "Security audit report (SOC2)", + Priority: "high", + AssignedTo: lisaSeller, + Status: "assigned", + }) + + // 9. Create sample answers for published requests + createAnswer(AnswerInput{ + RequestRef: "FIN-001", + Title: "Audited Financial Statements FY2023-2024", + Files: []string{"TechCorp_Audited_FY2023.pdf", "TechCorp_Audited_FY2024.pdf"}, + SubmittedBy: johnSeller, + ApprovedBy: mikeIB, + Status: "published", + }) + + createAnswer(AnswerInput{ + RequestRef: "IT-001", + Title: "TechCorp System Architecture", + Files: []string{"TechCorp_Architecture_2026.pdf"}, + SubmittedBy: lisaSeller, + ApprovedBy: alexIB, + Status: "published", + }) + + // 10. Create sample buyer questions + createBuyerQuestion(BuyerQuestionInput{ + Workstream: financeWS, + AskedBy: jamesBuyer, + Question: "Can you provide customer concentration analysis?", + Status: "pending", // No match, routed to IB + }) + + createBuyerQuestion(BuyerQuestionInput{ + Workstream: itWS, + AskedBy: emilaBuyer, + Question: "What's the tech stack?", + Status: "matched", // AI matched to IT-001 + MatchedTo: "IT-001", + }) + + // 11. Create activity events for timeline + createEvent(project, "Sarah Mitchell created Project Phoenix", sarahIB, -7*24*time.Hour) + createEvent(project, "Initial Finance DD requests sent to TechCorp (12 items)", mikeIB, -6*24*time.Hour) + createEvent(project, "John Smith accepted invitation", johnSeller, -5*24*time.Hour) + createEvent(project, "FIN-001 submitted by John Smith", johnSeller, -3*24*time.Hour) + createEvent(project, "FIN-001 approved and published to data room", mikeIB, -2*24*time.Hour) + createEvent(project, "Blackstone joined as buyer", jamesBuyer, -1*24*time.Hour) + createEvent(project, "Bain Capital joined as buyer", emilaBuyer, -12*time.Hour) +} +``` + +### 8.2 Demo Login Credentials + +| Role | Email | Password | What they see | +|------|-------|----------|---------------| +| IB Admin | sarah.mitchell@gs.com | Demo2026! | Full project dashboard, all workstreams | +| IB Member | mike.chen@gs.com | Demo2026! | Finance workstream, vetting queue | +| Seller Admin | john.smith@techcorp.com | Demo2026! | Task inbox with 12 requests | +| Seller Member | mary.johnson@techcorp.com | Demo2026! | Task inbox with 4 Finance requests | +| Buyer Admin | james.lee@blackstone.com | Demo2026! | Data room with 2 published docs | + +### 8.3 Demo Scenario Flow + +**For a live demo, walk through this sequence:** + +1. **Login as Sarah (IB Admin)** — Show project overview, team status, request progress +2. **Switch to Mike (IB Member)** — Show vetting queue, approve FIN-003, publish to data room +3. **Switch to John (Seller Admin)** — Show task inbox, respond to FIN-004 +4. **Switch to James (Buyer)** — Show data room, new document notification, ask a question +5. **Back to Mike** — Show buyer question routed, AI match suggestion + +**Demo data realism:** +- Mix of statuses (open → assigned → submitted → vetted → published) +- Some high-priority items +- Some overdue items (creates urgency) +- Comment threads on submitted items +- Activity timeline with realistic timestamps + +--- + +## 9. Implementation Notes + +### 9.1 API Endpoints Required + +``` +POST /api/register — Account creation +POST /api/verify — Email verification +POST /api/mfa/setup — Start MFA enrollment +POST /api/mfa/verify — Complete MFA enrollment +POST /api/login — Authenticate +POST /api/invitations/accept — Accept invitation +POST /api/projects — Create project (wizard) +POST /api/projects/:id/invites — Send invitations +POST /api/projects/:id/requests/import — Excel import +GET /api/tasks — My task inbox +PATCH /api/requests/:id — Update request status +POST /api/answers — Submit answer +PATCH /api/answers/:id/approve — Approve answer +PATCH /api/answers/:id/reject — Reject answer +``` + +### 9.2 Email Service + +- Use SendGrid or AWS SES +- All emails from: `noreply@muskepo.com` +- Reply-To: set to inviting user's email +- Unsubscribe links required (CAN-SPAM compliance) +- Track open/click rates for onboarding optimization + +### 9.3 Metrics to Track + +**Onboarding funnel:** +- Registration → Email Verified (%) +- Email Verified → MFA Complete (%) +- MFA Complete → First Project Created (%) +- First Project → First Request List (%) +- First Request → First Seller Response (time) + +**Activation:** +- Time to first request list created +- Time to first seller login +- Time to first answer submitted +- Time to first data room publication + +--- + +*This document defines the complete first-run experience. Implementation should follow these flows exactly — deviations require spec update first.* diff --git a/website/dpa.html b/website/dpa.html index 2d79b1e..e200f57 100644 --- a/website/dpa.html +++ b/website/dpa.html @@ -5,6 +5,17 @@ Data Processing Agreement — Dealspace + + + + + + + + + + + diff --git a/website/features.html b/website/features.html index 4991ff6..ab8287f 100644 --- a/website/features.html +++ b/website/features.html @@ -5,6 +5,17 @@ Features — Dealspace + + + + + + + + + + + @@ -53,7 +64,7 @@ Sign In Request Demo - diff --git a/website/llms.txt b/website/llms.txt new file mode 100644 index 0000000..8c24a1b --- /dev/null +++ b/website/llms.txt @@ -0,0 +1,21 @@ +# Dealspace +> AI-native M&A deal workflow platform for investment banks and advisors. + +Dealspace is a secure, encrypted deal management platform for M&A transactions. Investment banks use it to manage due diligence data rooms, track requests across workstreams, and collaborate with sell-side and buy-side parties. All data is encrypted end-to-end with per-project keys. + +## Features +- Secure virtual data room with watermarked document serving +- Request tracking across workstreams (Finance, Legal, IT, Operations) +- Role-based access: IB advisor, seller, buyer, analyst +- FIPS 140-3 encryption (AES-256-GCM, HKDF-SHA256) +- AI document matching (coming v1.1) +- MCP server for agent integration (coming v2.0) + +## Contact +- Waitlist: https://muskepo.com/#waitlist +- Pricing: https://muskepo.com/pricing +- Security: https://muskepo.com/security +- Privacy: https://muskepo.com/privacy + +## Optional +- API docs: https://muskepo.com/api (coming soon) diff --git a/website/mcp-manifest.json b/website/mcp-manifest.json new file mode 100644 index 0000000..31e56e1 --- /dev/null +++ b/website/mcp-manifest.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "name": "Dealspace", + "description": "M&A deal workflow platform", + "tools": [ + { + "name": "join_waitlist", + "description": "Join the Dealspace early access waitlist", + "input_schema": { + "type": "object", + "properties": { + "email": {"type": "string", "description": "Work email address"}, + "company": {"type": "string", "description": "Company or firm name"}, + "role": {"type": "string", "description": "Job title or role"} + }, + "required": ["email"] + } + }, + { + "name": "get_pricing", + "description": "Get Dealspace pricing information", + "returns": "Pricing tiers and details" + }, + { + "name": "get_security_info", + "description": "Get security and compliance information", + "returns": "FIPS 140-3 compliance, encryption details, audit capabilities" + } + ] +} diff --git a/website/pricing.html b/website/pricing.html index 8a8c8e3..9dcc064 100644 --- a/website/pricing.html +++ b/website/pricing.html @@ -5,6 +5,17 @@ Pricing — Dealspace + + + + + + + + + + + @@ -53,7 +64,7 @@ Sign In Request Demo -