Skip to main content

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-documents Storage 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_update RLS policy was dropped (RLS can't restrict columns), so a contractor can't self-escalate status. All contractor profile/onboarding writes go through vetted service-role server actions that update an explicit field allowlist (ADR-0004).
  • State machine: submitOnboarding requires a Wise account + a government ID, then moves the contractor to pending_activation; an admin activates (activateContractor) to active. 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.