KnowledgeVault AI module-spec M-01
M-01 delivers end-to-end expert registration for the KnowledgeVault platform: a mobile signup form collecting name, email, phone, trade, specialties and experience; the POST /api/experts/signup endpoint that atomically creates the experts row, a Supabase Auth user with role=expert in app_metadata, and a Stripe Connect Express account for payouts; and a reusable JWT auth middleware consumed by M-02 and M-04. This module is the cold-start unlock — no sessions, captures or payments can occur until
No items captured.
M-01 delivers end-to-end expert registration for the KnowledgeVault platform: a mobile signup form collecting name, email, phone, trade, specialties and experience; the POST /api/experts/signup endpoint that atomically creates the experts row, a Supabase Auth user with role=expert in app_metadata, and a Stripe Connect Express account for payouts; and a reusable JWT auth middleware consumed by M-02 and M-04. This module is the cold-start unlock — no sessions, captures or payments can occur until
No explicit evidence field yet. Require tests, screenshots, linked PRs, or reviewed outputs before marking complete.
No section body captured.
Machine-readable source fields
M-01 delivers end-to-end expert registration for the KnowledgeVault platform: a mobile signup form collecting name, email, phone, trade, specialties and experience; the POST /api/experts/signup endpoint that atomically creates the experts row, a Supabase Auth user with role=expert in app_metadata, and a Stripe Connect Express account for payouts; and a reusable JWT auth middleware consumed by M-02 and M-04. This module is the cold-start unlock — no sessions, captures or payments can occur until an authenticated expert identity exists.
M-01
| auth | path | errors | method | request | response |
|---|---|---|---|---|---|
| public — creates the account, no prior JWT required | /api/experts/signup | - Duplicate email — 409 { error: email already registered, code: duplicate_email, status: 409 } - Missing required field — 422 { error: field is required, code: missing_field, status: 422 } - Invalid email format — 422 { error: email is invalid, code: invalid_email, status: 422 } - Stripe API failure or timeout — 201 with stripe_onboarding_url: null and deferred: true (expert row committed, stripe_account_id = null) - Supabase Auth createUser failure — 500 { error: account creation failed, code: auth_error, status: 500 } with no experts row written | POST | JSON body: { name: string (required), email: string (required, valid email format), phone: string (optional), role_type: string (required, one of: Plumber | Electrician | HVAC Tech | Pipefitter | Millwright | Other), specialties: string[] (required, may be empty), years_experience: integer (optional, 0-60) } | 201: { expert_id: string (uuid), stripe_onboarding_url: string | null, deferred: boolean } |
auth.expert_id() is a SQL function installed in D-01-01: (auth.jwt()->app_metadata->>expert_id)::uuid. All downstream modules (M-02 sessions, M-04 payments) reference this function in their RLS policies — must not be dropped or renamed. experts.stripe_account_id is nullable — null means Stripe onboarding is deferred and payout cannot be released until expert completes bank connection.
| rls | name | indexes | purpose | key columns |
|---|---|---|---|---|
| RLS enabled. Policy experts_self_rw: SELECT and UPDATE allowed WHERE id = auth.expert_id(). INSERT allowed via BYPASSRLS (service-key only). DELETE not permitted. No public SELECT. | experts | - UNIQUE INDEX on email (enforced by UNIQUE constraint) | Stores registered expert profiles and Stripe Connect Express account references for payout routing. | - id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY - name text NOT NULL - email text UNIQUE NOT NULL - phone text NULL - role_type text NOT NULL - specialties text[] NOT NULL DEFAULT '{}' - years_experience integer NULL - bio text NULL - stripe_account_id text NULL - created_at timestamptz NOT NULL DEFAULT now() |
Auth & Expert Onboarding
| id | name | type | inputs | outputs | description |
|---|---|---|---|---|---|
| D-01-01 | 001_experts_table.sql | migration | - Supabase Postgres instance with M-00 migrations applied - supabase db push or psql execution context | - experts table in public schema with all specified columns and constraints - auth.expert_id() function in Supabase DB | Creates the experts table in the public schema with all required columns. RLS is enabled with a self-access policy using the auth.expert_id() helper, and service-key INSERT bypass. Also installs the auth.expert_id() SQL helper function used by downstream module RLS policies. |
| D-01-02 | POST /api/experts/signup | api_route | - JSON body: { name: string (required), email: string (required), phone: string (optional), role_type: string (required), specialties: string[] (required, may be empty array), years_experience: integer (optional) } | - 201 success: { expert_id: string (uuid), stripe_onboarding_url: string | null, deferred: boolean } - 409 on duplicate email - 422 on missing required field with field name in error body - 500 on Supabase Auth creation failure with no partial expert row written | Creates an expert record, a Supabase Auth user with role=expert in app_metadata, and a Stripe Connect Express account. Returns expert_id and stripe_onboarding_url. If Stripe fails, the expert row is still committed with stripe_account_id=null and deferred=true is returned. If Supabase Auth creation fails, no expert row is written and 500 is returned. Signup form has no password field — Supabase user is created with a random UUID password; all future logins use magic link (passwordless). |
| D-01-03 | expertAuthMiddleware | service | - HTTP request with Authorization: Bearer JWT header (Supabase-issued JWT) | - Mutated request with req.expert = { id: uuid } on valid expert JWT - 401 JSON error on missing or expired token - 403 JSON error on valid JWT with wrong role | Reusable Next.js API middleware that validates the Supabase Auth JWT from the Authorization: Bearer header, reads role from app_metadata, and attaches expert_id to the request context. Exported as a single importable function. Used by all /api/experts/*, /api/sessions/*, and /api/products/queue routes in M-02 and M-04. |
| D-01-04 | ExpertSignupFlow — screens S-01-a, S-01-b, S-01-c | component | - S-01-a: user-entered form values - S-01-b: stripe_onboarding_url from POST /api/experts/signup response - S-01-c: Stripe Connect return URL query params or deferred flag from API response | - S-01-a submits POST /api/experts/signup then navigates to S-01-b on success or shows inline field error on failure - S-01-b fires external redirect to stripe_onboarding_url - S-01-c CTA navigates to product queue (S-02-a route) | Three-screen mobile signup flow. S-01-a (Expert Signup Form): full-screen single-column scroll with sticky submit button, fields for name, email, phone (optional), trade selector (bottom-sheet picker: Plumber, Electrician, HVAC Tech, Pipefitter, Millwright, Other), specialties multi-select (bottom-sheet, chips), years experience (optional numeric 0-60), and submit CTA. Inline 409 error on email field shows correct copy. S-01-b (Stripe Redirect Interstitial): centered loading message, fires redirect to stripe_onboarding_url within 500ms of mount. S-01-c (Onboarding Confirmation): centered succe |
| D-01-05 | Supabase Auth JWT claim config — role=expert in app_metadata | config | - Supabase Auth admin API (SUPABASE_SERVICE_KEY) - experts table with at least one test row (created via D-01-02) | - Expert JWTs with app_metadata.role = expert and app_metadata.expert_id = uuid - Verified auth.expert_id() SQL function returning correct UUID in RLS context - Test confirming JWT structure and middleware reads from app_metadata not user_metadata | Documents and validates the JWT claim structure established in D-01-02. Supabase Auth automatically includes app_metadata fields in the issued JWT — no custom hook required. Claim structure: app_metadata.role = expert, app_metadata.expert_id = experts.id uuid. The auth.expert_id() SQL helper (installed in D-01-01) reads expert_id from JWT for use in RLS policies across all modules. Deliverable includes a test confirming JWT structure and that middleware reads from app_metadata not user_metadata. |
| calls | purpose | service |
|---|---|---|
| - POST /v1/accounts — { type: express, capabilities: { transfers: { requested: true } }, business_type: individual, email: expert_email } — creates Express account, response.id stored as experts.stripe_account_id - POST /v1/account_links — { account: stripe_account_id, refresh_url: NEXT_PUBLIC_BASE_URL/experts/signup/stripe-refresh, return_url: NEXT_PUBLIC_BASE_URL/experts/onboarding-complete, type: account_onboarding } — returns url returned as stripe_onboarding_url in API response | Create Express payout accounts for experts so no bank details are stored on the platform. Stripe handles all KYC and bank verification. | Stripe Connect |
| - supabase.auth.admin.createUser({ email: expert_email, password: crypto.randomUUID(), app_metadata: { role: expert, expert_id: uuid }, email_confirm: true }) — creates Auth user, sets app_metadata claims, marks email as pre-confirmed | Create authenticated user accounts for experts and issue JWTs with role=expert in app_metadata. Future logins use magic link (passwordless) — no password set by expert at signup. | Supabase Auth |
- M-00 complete: Supabase project initialized, database connection accessible, and base schema migrations applied
- Supabase Auth enabled with email/password provider and admin API access (SUPABASE_SERVICE_KEY in environment)
- Stripe account with Connect enabled — STRIPE_SECRET_KEY present in environment
- Environment variables set: SUPABASE_URL, SUPABASE_SERVICE_KEY, STRIPE_SECRET_KEY, NEXT_PUBLIC_BASE_URL
No items captured.
1.0