14. Contractor onboarding and document storage
Date: 2026-06-06
Status
Accepted
Context
After accepting an invitation (status onboarding), a contractor completes a multi-step wizard before an admin activates them. It collects personal details, payment details, and identity documents — sensitive PII and financial data — so the design must minimize what's stored and keep documents private.
Decision
- Wizard (
/contractor/onboarding,OnboardingWizard): four steps — Personal → Payment → Documents → Review & submit — single-column, labels above inputs, inline errors, progress bar, with Back navigation. Each step saves to the DB, so the wizard is a server-persisted draft (prefilled on return). - Bank minimization: the contractor enters their account number but only the last 4 digits are persisted (
bank_account_last4); the full number is discarded. Wise account email is required. The Wise referral link is shown on the payment step. - Documents: uploaded directly from the browser to a private
contractor-documentsStorage bucket; storage RLS restricts writes/reads to the contractor's own<contractor_id>/…folder (admins may read their company's). A server action records the metadata after re-checking the path. Downloads use short-lived signed URLs (no public access). - Contractor writes are server-only: the permissive
contractors_self_updateRLS policy was dropped (RLS can't restrict columns), so a contractor can't self-escalatestatus. All contractor profile/onboarding writes go through vetted service-role server actions that update an explicit field allowlist (ADR-0004). - State machine:
submitOnboardingrequires a Wise account + a government ID, then moves the contractor topending_activation; an admin activates (activateContractor) toactive. The contractor home routes by status (onboarding → wizard; pending/archived → banner; active → home). - DocuSeal e-signature is stubbed in the review step pending DocuSeal credentials (open item / Step for renewals).
Consequences
- Full bank account numbers are never stored; documents are private and owner-scoped by RLS.
- Privilege escalation via direct row updates is closed; the invite → onboard → activate lifecycle is enforced server-side.
- The document upload and full wizard flow can only be exercised end-to-end with a real authenticated contractor (created via the invite flow), which depends on admin SSO being configured; build/typecheck/lint and route-gating are verified.
- DocuSeal integration remains outstanding before go-live.