# 🔨 Launch Week Readiness Audit — 26 Feb 2026
## Final Technical Review — 48-72 Hours to Go Live

**Auditor:** Builder  
**Codebase:** ~25,000 lines across 36 pages, 28 API routes  
**Build status:** ✅ GREEN  
**Verdict:** **LAUNCH-READY with caveats** — 3 P0 fixes recommended before go-live

---

## 🚨 P0 — Fix Before Launch (Could Cause Real Damage)

### 1. RACE CONDITION: Payment Create Temporarily Marks Match as "Hired"
**File:** `src/app/api/payments/create/route.ts` lines 117-140  
**Impact:** CRITICAL — data integrity + user confusion  
**Issue:** To work around a DB trigger that requires `match.status = 'hired'` before inserting a payment record, the code:
1. Sets match status to `'hired'` (Step 1)
2. Inserts the payment record (Step 2)
3. Reverts match to `'accepted'` (Step 3)

If the request crashes between Step 1 and Step 3 (server restart, OOM, timeout), the match stays permanently in `'hired'` status **without any payment**. The worker gets notified they're hired when they're not. The webhook fires on the orphaned "hired" match and double-notifies.

**Fix:** Either:
- (a) Remove the DB trigger and insert payments without the status check, OR
- (b) Wrap Steps 1-3 in a Supabase RPC with `BEGIN/COMMIT` so it's atomic, OR
- (c) Use a `pending_payment` status instead of `hired` as the temporary state

**Severity justification:** Under load with Stripe webhook retries, this WILL happen. Not a matter of if.

### 2. NO HEALTH CHECK ENDPOINT
**Impact:** HIGH — No automated monitoring, no alerting  
**Issue:** No `/api/health` endpoint exists. If the app goes down at launch, nobody knows until a user complains. BetterStack/UptimeRobot can't monitor anything.

**Fix:** Add a simple `GET /api/health` that returns 200 + basic checks (can reach DB, Stripe key configured). 10-minute task.

### 3. CONFIRM-HIRE ENDPOINT LACKS CSRF PROTECTION
**File:** `src/app/api/payments/confirm-hire/route.ts`  
**Impact:** MEDIUM-HIGH — CSRF attack vector on money flow  
**Issue:** `confirm-hire` exports a raw `POST` handler without `withCSRFProtection()`. The `payments/create` endpoint correctly uses CSRF. But confirm-hire is called from the payment-success page client-side and is unprotected. An attacker with a valid `session_id` could craft a CSRF to force-confirm hires.

**Mitigating factor:** The endpoint verifies Stripe `payment_status === 'paid'` and `session.metadata.match_id`, so exploitation requires a valid paid Stripe session. Risk is moderate but the pattern inconsistency is a bad look.

**Fix:** Wrap with `withCSRFProtection(handler)` like all other mutation endpoints.

---

## ⚠️ P1 — Fix This Week (Launch Quality Issues)

### 4. REALTIME NOTIFICATIONS DISABLED
**File:** `src/hooks/use-notifications.ts` lines 152, 227  
**Impact:** Workers don't see hire notifications in real-time — have to refresh  
**Issue:** Two `TODO: Re-enable real-time updates when needed` comments. Supabase realtime subscriptions are commented out. Notifications only load on page mount.

**Fix:** Either re-enable Supabase realtime OR add a 30-second polling interval. Workers need to see "You're hired!" without refreshing.

### 5. NO WELCOME EMAILS ON SIGNUP
**Impact:** User retention gap  
**Issue:** `EMAIL_TEMPLATES.welcomeWorker` and `EMAIL_TEMPLATES.welcomeContractor` exist in the email library but are never called. No email is sent on signup completion.

**Fix:** Add `sendEmail()` call at the end of contractor signup (`handleConfirm`) and worker signup (`saveWorkerProfile`) flows.

### 6. PAYMENT CREATE: match_status WORKAROUND ERRORS SILENTLY
**File:** `src/app/api/payments/create/route.ts` line 126  
**Issue:** `if (!matchUpdateError)` — if the temporary "hired" update fails, the entire payment/upsert block is skipped, but the function still returns a checkout URL. The user goes to Stripe, pays, but no payment record exists. Webhook will throw "Payment not found for intent."

**Fix:** Return an error if Step 1 fails: `if (matchUpdateError) return 500`.

### 7. REJECT ENDPOINT MISSING CSRF + RATE LIMIT
**File:** `src/app/api/match/reject/route.ts`  
**Impact:** Unprotected mutation endpoint  
**Issue:** Raw `POST` export — no `withCSRFProtection`, no rate limiting. An attacker could mass-reject all matches for a contractor.

**Fix:** Wrap with CSRF protection. Add rate limit (e.g., 50 rejects per 15 min).

### 8. NO-SHOW ENDPOINT MISSING CSRF
**File:** `src/app/api/match/no-show/route.ts`  
**Same pattern as above.

---

## 📋 P2 — Technical Debt (Post-Launch)

### 9. DUPLICATE WORKER PROFILE QUERIES IN CONFIRM-HIRE
**File:** `src/app/api/payments/confirm-hire/route.ts`  
**Issue:** Worker profile is fetched twice — once for SMS (line ~166, selecting `phone`) and once for email (line ~191, selecting `email, name`). Should be a single query.

### 10. CONVERSATION CREATION DUPLICATED IN TWO FILES
**Files:** `confirm-hire/route.ts` and `webhooks/stripe/route.ts`  
**Issue:** Identical 30-line conversation creation logic copy-pasted. If one gets fixed, the other won't. Extract to a shared function like `getOrCreateConversation()`.

### 11. CLIENT-SIDE console.log/console.error CALLS REMAIN
**Files:** `onboarding-pack/client.tsx`, `contractor/error.tsx`, `PackGenerator.tsx`, `notification-bell.tsx`, `use-notifications.ts`  
**Issue:** 7 instances of `console.error`/`console.warn` instead of the app's `logger`. Not critical but messy for production.

### 12. CONTRACTOR SIGNUP HAS A SECOND AUTH FORM
**File:** `src/app/(authenticated)/contractor/signup/page.tsx`  
**Issue:** The contractor signup page has its own "auth" step that creates an account, duplicating the main `/auth/signup` page. Users coming from `/auth/signup` (who chose "contractor") will see step="company" (correct), but the auth step code is dead weight that could confuse maintenance.

### 13. PAYMENT AMOUNT NOT DISPLAYED ON LANDING PAGE
**File:** `src/app/page.tsx`  
**Issue:** The landing page mentions "$50" in copy, but uses a hardcoded string not the `PAYMENT_AMOUNT` constant. If the price changes, landing page won't update.

### 14. SERVICE WORKER REGISTRATION SWALLOWS ERRORS
**File:** `src/app/layout.tsx` line 85  
**Issue:** `navigator.serviceWorker.register('/sw.js').catch(() => {})` — completely silent failure. If the service worker breaks, no logging.

### 15. RATE LIMIT MEMORY LEAK POTENTIAL ON LONG-RUNNING PROCESS
**File:** `src/lib/rate-limit.ts`  
**Issue:** Cleanup interval runs every 5 minutes, but the in-memory store grows unbounded between cleanups. Under high traffic, the Map could consume significant memory. Already handled with Redis in prod (Upstash configured), but if Redis goes down, the fallback will consume memory.

---

## ✅ What's Solid

| Area | Status | Notes |
|------|--------|-------|
| **Auth flow** | ✅ Excellent | Proper `getUser()` everywhere, no `getSession()` on server |
| **CSRF protection** | ✅ Good | 4/7 mutation endpoints protected (3 flagged above) |
| **Rate limiting** | ✅ Good | Redis-backed with memory fallback, applied to critical paths |
| **Payment flow** | ✅ Good | Stripe Checkout, webhook + client-side confirm, idempotent |
| **Input sanitization** | ✅ Good | `stripContactInfo()` on job posts, UUID validation, message length limits |
| **Error boundaries** | ✅ Present | `error.tsx` at authenticated root, contractor, worker levels |
| **Mobile UX** | ✅ Excellent | h-12+ buttons, 48px touch targets, sticky CTAs, swipe gestures |
| **ABN verification** | ✅ Solid | Real ABR lookup, checksum validation, AI enrichment |
| **Profile completeness** | ✅ Implemented | Gate on apply, checklist on dashboard |
| **Email + SMS notifications** | ✅ Wired | Both fire on hire (confirm-hire + webhook) |
| **Open redirect prevention** | ✅ Present | Auth callback validates `next` param |
| **Conventional commits** | ✅ Clean | Consistent git history |

---

## 🎯 Recommended Launch Day Sequence

1. **Fix P0 #1** (race condition) — 30-60 min
2. **Fix P0 #2** (health endpoint) — 10 min  
3. **Fix P0 #3** (CSRF on confirm-hire) — 5 min
4. **Fix P1 #6** (silent failure on payment create) — 5 min
5. **Fix P1 #7-8** (CSRF on reject + no-show) — 10 min
6. Build, deploy, verify
7. Set up BetterStack/UptimeRobot on `/api/health`
8. **Fix P1 #4** (realtime notifications) — 30 min
9. **Fix P1 #5** (welcome emails) — 15 min

**Total estimated time: ~2.5 hours for all P0 + P1 fixes.**

---

## Summary

The codebase is well-structured and production-quality in most areas. Auth, payments, and UX are solid. The three P0 issues are surgical fixes — the race condition in `payments/create` is the only one that could cause real user damage under load. 

For a construction hiring app launching to its first users: **this is launch-ready after the P0 fixes.** The P1 items are quality-of-life improvements for the first week.

*Build: GREEN | Pages: 36 | Routes: 28 | Audit complete 🔨*
