Owner: Partnerships / Growth (platform admin) | Review: Quarterly
A partner moves through a tracked, auditable pipeline: admin sends a tokenised proposal, the partner reviews it (engagement is tracked per section), the partner self-signs an NDA to unlock the document vault, and the admin works the relationship on a kanban until the deal is closed and the partner is activated. Every external touch is logged to a timeline, and NDA signatures carry an Oman Electronic Transactions Law evidence chain.
Pipeline stages
The real status enum (partners.status) โ note the proposal comes before the NDA, and the NDA is partner-initiated, not emailed for signing:
draft โ proposal_sent โ meeting_scheduled โ nda_signed โ mou_signed โ active
โ on_hold (lost / stalled)
Tracked in Admin โ Partnerships (kanban, one column per status; drag a card to move it).
Step-by-step
Create partner & issue a proposal token
Create the partner record, then `issueProposalToken` mints a signed **30-day JWT**. Prefer tokens over legacy access codes (codes are deprecated, accepted only while `LEGACY_PROPOSAL_CODES_ACCEPTED=true`). Status: `proposal_sent`.
Send the invitation
`sendInvitation` emails a branded message with the token proposal link and the Cal.com booking link. Logged to the timeline.
Partner reviews the proposal (tracked)
Partner opens `/partnership/:id?token=โฆ` โ content is **server-only** (never shipped before unlock). An IntersectionObserver fires `trackView` per section (hero, terms, metrics, value, modules, why, next_steps, cta, documents) with time-on-section. First open triggers an admin first-view alert; the kanban card shows the view count. Full vault stays NDA-gated.
Partner signs the NDA
Partner goes to `/sign/:id/nda` โ 4 steps: contact info โ review the bilingual (EN/AR) NDA (scroll-to-unlock) โ typed signature โ confirmation (ref `NDA-00001`). `submitNda` records a `partnerNdaRequests` row (validity **+3 years**), captures **IP + User-Agent** (Oman Electronic Transactions Law, RD 69/2008), writes an `nda_signed` timeline entry, auto-creates a "NDA โ Signed" vault document, **auto-advances status to `nda_signed`**, and sends dual emails (admin alert + signer's official record).
Grant portal access
`sendPortalAccess` emails the `/portal/{token}` link โ a deterministic **HMAC-SHA256 token link** (no password/credentials). The partner sees the timeline, NDA history, vault documents (download-audited), and action items they can mark done.
Work the pipeline & close
On the kanban: `addMeeting` logs a meeting and auto-creates action items + an agenda email; `addAction` / `addDecision` capture follow-ups. Move `nda_signed โ mou_signed` (stamps `mouSignedAt`). `closeDeal('won')` โ `active`; `closeDeal('lost'|'stalled')` โ `on_hold`. On `active`, the NDA vault gate lifts and the timeline becomes the operational log.
NDA evidence chain
Each signature is stored with enough to stand up under Oman's Electronic Transactions Law:
| Captured | Field |
|---|---|
| Typed signature | signature |
| Signer identity | contactName, contactEmail, contactTitle |
| IP address | logged at submit |
| User-Agent | logged at submit (migration 0150) |
| Timestamps | signedAt (UTC + Oman local), expiresAt (+3 years) |
| Reference | NDA-00001 (padded id) |
Rate limits: proposal unlock 5 attempts/hr/IP; NDA submit 3/hr/IP.
Live partners
mol ยท mocipi ยท occi ยท ooredoo ยท bank-sohar ยท usman โ content lives in server/data/partnership-content.ts (server-only).
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Proposal token rejected | 30-day JWT expired | Re-issue via issueProposalToken, re-send invitation |
| Partner can't unlock | 5 attempts/hr limit hit | Wait out the window or send a fresh token link |
| Legacy access code stopped working | LEGACY_PROPOSAL_CODES_ACCEPTED=false in prod |
Migrate the partner to a token link |
| NDA submit failing | 3/hr limit hit | Retry after the window |
| Partner wants vault before signing | requiresNda=true docs are gated |
Direct them to sign the NDA first |
| Email not sending | Resend key issue | Check emailConfig (masked key + last-used); send a testEmail |
| Need to reset a demo partner | โ | resetPartnerTestData โ never run against a real partner |
Key files
| File | Purpose |
|---|---|
server/routers/partnerships.ts |
All partner procedures + email generation |
server/data/partnership-content.ts |
Server-only proposal content |
client/src/pages/PartnershipProposalPage.tsx |
Public tokenised proposal page + view tracking |
client/src/pages/PartnerNdaSignPage.tsx |
4-step NDA signing flow |
client/src/pages/admin/PartnershipsAdminPage.tsx |
Admin kanban + timeline |