# Workershubb — Project Context for Claude Code

## What this is
Workershubb is an on-demand service marketplace connecting clients in Lagos, Nigeria with vetted independent agents (home cleaning, laundry, errands/groceries, plumbing, electrical — pilot categories). This document is read automatically at the start of every Claude Code session in this project — keep it updated as decisions change.

## Stack (fixed decisions — do not switch without asking)
- **Backend:** Native PHP (no framework) — chosen deliberately over the Node.js/NestJS originally suggested in the product brief, since the developer is comfortable in PHP and the mobile app team only needs a clean REST API, not the backend source.
- **Database:** MySQL (via XAMPP locally; confirm equivalent on live hosting before deploying — the original brief specifies PostgreSQL, but MySQL was chosen for local dev simplicity)
- **Real-time:** Ratchet (PHP WebSockets) OR a small standalone Node/Socket.io microservice used only for live tracking/chat — decide and lock this in during Stage 8, then update this file
- **Frontend (web):** Server-rendered PHP views using a shared design system (see Stage 0B), not Next.js/React — the public site and admin dashboard are PHP-rendered
- **Mobile:** Built separately by another developer in React Native (iOS + Android), consuming this project's REST API only. They never touch this PHP codebase directly.
- **Payments:** Paystack and/or Flutterwave — cards, bank transfer (virtual account per booking), USSD, and the transfer API for agent payouts
- **Notifications:** WhatsApp Business API (primary), SMS (fallback), push via FCM/APNs, email (admins only)

## Critical business rules (do not deviate from these without confirming)
- **Commission: 30%.** Client pays the listed price; agent receives 70%. Calculated and stored on the payout record at payout-creation time, not earlier.
- **No escrow.** Clients pay directly into the Workershubb company account. Money is tracked via an internal ledger (`ledger_entries` table), and payouts are a separate step after job confirmation — never conflate "payment received" with "agent has been paid."
- **Vetting is guarantor-based, NOT NIN/BVN.** Every agent needs: ID photo + selfie match, two guarantors (each verified by phone call from the Safety admin role), an admin interview, and a skills check for trade categories. No NIN/BVN verification API is used in this project.
- **Booking only moves forward on real trigger events** — payment confirmed, agent accepts, client confirms. Never let any client/frontend code set a booking's status directly; every transition goes through a guarded state-machine function.
- **Phone numbers are never shown to either party.** Calls go through a masked-number system (telephony provider TBD); chat stays in-app.
- **Pricing types:** fixed package, fixed time block, or call-out fee + approved quote (for trades). Bookings keep the price they were created with even if the price list changes later.

## Folder structure
```
/public              ← web root (index.php front controller, /assets for CSS/JS/images)
/app/Controllers
/app/Models
/app/Middleware
/app/Views/{public,client,agent,admin}
/api/v1              ← versioned REST API for the mobile app
/includes            ← db.php, auth.php, middleware.php, functions.php, validate.php, csrf.php
/classes             ← domain objects (User, Booking, Payment, etc.)
/services            ← third-party API wrappers (Paystack, WhatsApp, etc.)
/database            ← schema.sql, seeders.sql
/config
/storage/uploads     ← OUTSIDE web root if hosting allows — ID docs, selfies, guarantor forms
/storage/logs
```
`/includes`, `/classes`, `/services`, `/database`, `/config`, and `.env` must never be directly web-accessible — block via `.htaccess` if they can't sit outside the web root.

## Build order (see the full stage prompts document for details)
0. Project skeleton
0B. Design system (colors, typography, components — see brief Section 13)
1. Database schema
2. Auth, roles, JWT + admin 2FA
3. Agent onboarding & vetting workflow
4. Catalog, pricing & booking flow (+ home page and booking page frontend)
5. Payments & ledger — **highest-risk stage, review carefully**
6. Dispatch & matching engine
7. Admin dashboard, all 9 roles (+ dashboard frontend)
8. Real-time tracking, chat, notifications
9. REST API finalization & mobile-handoff documentation
10. Security review, testing, deployment prep — **review carefully**

## Environment
- Local dev: XAMPP (MySQL, Apache, PHP)
- Live hosting: TBD — update this section once chosen, including whether it's shared hosting or a VPS (VPS will be needed once real-time/WebSockets are in use, since shared hosting usually can't run persistent socket connections)
- `.env` holds all secrets (DB credentials, JWT secret, Paystack/Flutterwave keys, WhatsApp Business API credentials) — never commit this file; keep `.env.example` updated with the required keys (no real values)

## Known open items (from the brief, still unresolved — flag if a stage depends on one of these)
- Payout timing: instant, next-day, or weekly — not yet decided
- Exact cancellation fee and no-show penalty amounts — not yet decided
- Who bears gateway transaction fees (brief recommends Workershubb absorbs it from the 30% commission)
- Final pilot zones within Lagos
- Minimum smartphone spec required for agents
- Legal/regulatory structure for holding client funds with no escrow (brief explicitly says this needs a Nigerian legal/fintech adviser — not something to resolve in code)

## Decisions made during the build (keep updating this section each stage)
- **Stage 2:** Admin 2FA has no separate "enrollment" step in the original brief, so one was added: on an admin's first successful password login (no `two_factor_secret` saved yet), they're routed to `/admin/setup-2fa` to scan a QR code and confirm a code before the secret is persisted. Every login after that goes through the normal `/admin/login/2fa` verify step.
- **Stage 2:** Refresh tokens are single-use and rotated on every `/auth/refresh` call (old one revoked, new one issued) — tighter than just letting one refresh token live for its full TTL.
- **Stage 2:** Rate limiting is a plain `rate_limit_attempts` DB table, not Redis — fine at pilot scale; revisit if/when Redis is introduced for geo-matching (Stage 6).
- **Stage 2:** OTP codes and admin password-reset links are logged to `storage/logs/*.log` instead of actually sending via WhatsApp/SMS/email — real sending is wired up in Stage 8. In local/non-production `.env`, the OTP code is also returned directly in the API response for easy testing (`dev_otp` field) — this must never ship to production.
- **Stage 3:** The router is still a flat exact-match table (no dynamic `:token`-style path params yet), so the public guarantor form lives at `/guarantor-form?token=...` (query string, plain HTML form) rather than Section 16's documented `/api/v1/guarantor-forms/:token` JSON shape. Guarantors aren't mobile-app users, so a server-rendered form fits them better anyway — real dynamic path routing gets built in Stage 9 for the actual mobile-facing API surface.
- **Stage 3:** Guarantor invite links are generated by the platform but handed back to the AGENT to forward personally (via their own WhatsApp) rather than the platform messaging a stranger's number directly — the agent already has a personal relationship with their guarantors, so this is both simpler and more natural than the system cold-contacting them. Stage 8 can add platform-sent WhatsApp invites later if preferred.
- **Stage 3:** `documents_check -> guarantor_pending` is a deliberate Agent-Management-only gate (`approveDocuments()`), separate from the agent's own guarantor-invite action — matches Section 7's "Owner: Agent Management" for the documents-check row, so an agent can't self-approve their own documents by simply inviting guarantors.
- **Stage 3:** Probation length defaults to 14 days (`PROBATION_DAYS` in `config/app.php`) — not specified in the brief, treat as a placeholder pending a real decision from Operations.
- **Stage 3:** Sensitive files (ID photos, selfies, guarantor documents) are served only via signed, time-limited URLs (`FileUploadService`) with every access logged to `file_access_logs` — never a direct public path, per Section 17.

## Style/output preferences for this project
- MySQL syntax, not PostgreSQL, throughout
- Every admin action must write to `audit_log` — no exceptions
- Every payment/webhook function must be idempotent
- Prefer small, reviewable commits per logical change over large batched commits
