The work was completed. Here is the full deliverable, resent as a single message.
HALO CRM — Domain Map & Capability Inventory
Verified totals (re-run against /Users/shaansisodia/SISO_Workspace/SISO_Agency/clients/halocrm/repo): 79,980 LOC (src + convex, excl. _generated) · 38 pages · 36 convex modules (35 + tsconfig) · 56 schema tables · 43 Supabase importers · 511 any usages · 105 (api as any) casts · 0 test files · 159 console.log.
CLAUDE.md accuracy: unusually high — the Drive taxonomy, markUsed ordering, provisioning idempotency, and auth-naming sections all check out against source. Three drifts: it claims ~70 Supabase importers (actual 43), ~55 tables (actual 56), and lists payroll as Supabase-owned when convex/payroll.ts (330 LOC) exists — but see §2, it has zero frontend consumers.
1 & 2. Domain inventory + maturity
| Domain | Pages | Components | Convex | Tables | ~LOC | Maturity | Evidence |
|---|---|---|---|---|---|---|---|
| Shared Drive / content pipeline | SharedDrive, CreatorUpload | shared-drive/ | driveClient, googleDrive, driveScan, driveIndex, driveContent, driveStructure, crons | driveCreatorFolders, driveFiles, driveCreatorStats | ~2.5k | real-and-working | Live Drive v3 REST + OAuth refresh token; cron reconcile convex/crons.ts:15; idempotent provisioning scheduled not awaited |
| Gamification / quests | TasksRewards | gamification/ (5,886) | gamification, gamificationSeed | 13 gamification* tables | ~7.6k | real-and-working | Largest single domain; XP/banana ledgers, quest re-roll, shop |
| Referrals + referral invoicing | Referrals, ReferralInvoicing | referrals/, referral-invoicing/ | referrals, referralInvoicing, referralDemo | referralPartners, referralPayments, referralLinks | ~3.7k | real-and-working | src/pages/Referrals.tsx:61 live Convex; separate referralDemo seeder |
| Creator invoicing | CreatorInvoicing, CreatorInvoice | creator-invoicing/ (4,581) | invoicing, currency | creatorInvoicing, currencyRates | ~5.6k | real-and-working | Multi-currency w/ cached daily rates, server-side key |
| Payment log | PaymentLog (871) | payment-log/ | paymentLog | paymentLogEntries, paymentLogConfirmations, recurringExpenses | ~2.5k | real-and-working | Largest page component |
| AI voice cloning | AIVoice (788) | voice/ | voice (628) | voiceSources, generatedVoiceClones, voiceJobs | ~1.5k | partial — split | Convex path calls ElevenLabs live; but src/services/voice-clone/synthesize.ts:71 still invokes Supabase edge fn voice-generate |
| Creator onboarding | CreatorOnboarding, CreatorOnboardForm, CreatorOnboardQueue | onboarding/ (4,052) | onboardingSubmissions, creatorInvitations | onboardingSubmissions, creatorInvitations | ~4.4k | real-and-working | accept triggers Drive provisioning |
| Contracts / e-sign | Contracts, ContractSigning | contracts/ | contracts | contracts | ~1.3k | real, weak crypto | Canvas src/components/contracts/SignaturePad.tsx, token link flow; token gen is broken (§4) |
| Customs tracker | CustomsTracker | customs/ | customs | customs, customStatusHistory | ~2.3k | real-and-working | src/pages/CustomsTracker.tsx:21 live Convex |
| Payroll / attendance | Payroll | payroll/ (4,263) | payroll (330) | attendance, salesTracker | ~4.7k | partial — orphaned backend | convex/payroll.ts has 0 api.payroll.* consumers; 15 of 18 payroll components still import Supabase |
| Team / employees | Team, TeamMemberProfile/Edit/Onboarding | team/, employees/ | teamMembers | teams, teamAssignments, teamMemberRoles | ~4.3k | real, partial | "Activity tracking coming soon" src/pages/TeamMemberProfile.tsx:274 |
| Secure logins (credential vault) | SecureLogins | secure-logins/ | socialMediaLogins, secureArea | socialMediaLogins, secureAreaPasswords | ~1.5k | real-and-working, insecure | Passwords stored plaintext convex/socialMediaLogins.ts:12 |
| Access control / RBAC | AccessControlPanel, UserManagement | admin/ | rolePermissions, accessUsers | rolePermissions, profiles | ~2.8k | partial — two systems | §4 |
| Auth | Login, Register, Forgot/ResetPassword, Invitation | auth/ (129) | haloAuth (49) | authUsers, profiles | ~900 | partial, insecure | updateCredentials/createTeamMember stubbed to toast src/context/SupabaseAuthContext.tsx:229,254 |
| Creator analytics | CreatorAnalytics (62) | analytics/ | — | — | ~1.4k | demo-seeded-only | src/pages/CreatorAnalytics.tsx:21 — mockEngagementStats["1"], hardcoded to first mock record, no backend at all |
| Messaging | Messages (66) | messages/ | — | — | ~1.1k | stub-or-vestigial | src/pages/Messages.tsx:22 reads process.env.N8N_WEBHOOK_URL — undefined in Vite (needs import.meta.env), so webhookUrl is always '' and every send POSTs to the empty string |
| Dashboard | Dashboard (55) | dashboard/ | dashboard (42) | — | ~400 | partial | Thin |
Orphaned Convex modules (0 frontend references, verified by loop): payroll, driveScan, googleDrive, driveStructure, driveClient, demoSeed, demoFixes, resetHalo, seed, gamificationSeed, referralDemo, crons. Most are legitimately server-only (crons/seeders/internal actions) — payroll is the one genuine orphan: a complete, well-documented 330-LOC rewrite that nothing calls.
3. Build-vs-buy line — the 10x surface
(b) Commodity — replaceable by OSS, ~40k LOC of the 80k:
| Capability | Current cost | Notes |
|---|---|---|
| Auth + session | ~900 LOC bespoke | convex/haloAuth.ts is 49 lines of homemade crypto. Convex Auth / Better Auth / Auth.js is a drop-in. Highest-value swap. |
| RBAC / permissions | ~2.8k, two systems | CASL or Convex-native ctx.auth collapses both |
| E-sign | ~1.3k | Documenso / OpenSign are complete OSS e-sign with audit trails, tamper-evidence, PDF certs |
| Invoicing + multi-currency | ~5.6k | Invoice Ninja / Crater; currency caching is a solved problem |
| Payroll / attendance | ~4.7k | Frappe HR / OrangeHRM cover attendance, payslips, commission |
| Credential vault | ~1.5k | Vaultwarden / Passbolt. Current impl stores plaintext with a hardcoded fallback — the single strongest buy case |
| File explorer / Drive UI | ~830 + Supabase stack | Filestash, Nextcloud |
| PDF generation | 13 files, jspdf+html2canvas | react-pdf / Gotenberg; html2canvas rasterizes text (unsearchable, poor print) |
| Spreadsheet import/export | xlsx across ~6 files | Fine as a library; consolidate to one adapter |
| Notifications | 54 LOC + toasts | Novu |
| Audit log | ~none | No audit trail anywhere despite money + credentials. Greenfield: adopt, don't build |
| Cron/scheduling | Convex native | Already correct — keep |
| Analytics dashboard | ~1.4k mock | Nothing real exists; adopt Tremor/Metabase rather than build |
| Messaging | ~1.1k broken | Novu or plain n8n wiring |
(a) Genuinely bespoke — keep and protect (~15k LOC):
- Drive taxonomy + reconcile loop (
convex/driveStructure.ts,convex/driveScan.ts,convex/driveContent.ts) — the creator content pipeline. No OSS equivalent; this is the actual product. - Gamification/quest engine (~7.6k) — chatter motivation mechanics specific to the vertical.
- Referral partner ledger + remittance — agency-specific commission model.
- Customs tracker — creator-agency-specific order workflow.
- Creator onboarding → Drive provisioning chain — the integration is the value.
4. Architectural debt / risk register
S1 — The entire Convex backend is unauthenticated. grep -rn "ctx.auth\|getUserIdentity" convex/ returns 0 across all 35 modules; no convex/auth.config.* exists. Every query and mutation is a public internet endpoint. Anyone with the deployment URL can call socialMediaLogins.listByCreatorEmail and receive OnlyFans/Instagram credentials in plaintext, or call payroll/paymentLog mutations. All permission checks are client-side React (src/pages/Payroll.tsx:24, src/utils/permissionUtils.ts:11) and therefore advisory only. This is the top finding — it subsumes every other auth issue.
S2 — Credential vault is plaintext + hardcoded master password. convex/socialMediaLogins.ts:12 returns password: login.password — stored and returned unencrypted. src/hooks/useSecurePasswordManager.ts:43-45: when no hash row exists, verifySecurePassword falls back to password === "HaloVault2026!" — a hardcoded literal in the public bundle. Worse, secureArea.activePasswordHash (convex/secureArea.ts:6) is a public query returning the hash itself, and verification happens client-side (src/hooks/useSecurePasswordManager.ts:47), so the gate is bypassable from devtools regardless.
S3 — Password hashing is unsalted/single-round SHA-256. src/utils/passwordUtils.ts:13 — plain SHA-256, no salt, no iterations, for the vault. convex/haloAuth.ts:28 is marginally better (per-user salt) but still 1 round of SHA-256, GPU-crackable at ~10^10 guesses/sec. No bcrypt/scrypt/argon2. No rate limiting anywhere in haloAuth.login — unlimited credential stuffing. Comparison at convex/haloAuth.ts:29 is non-constant-time.
S4 — Contract signing tokens have far less entropy than they appear. convex/contracts.ts:6-10: 20 random bytes, but each is rendered byte.toString(36).padStart(2,"0"). Values 0–35 produce a 1-char string padded to 2; values ≥36 produce 2 chars unpadded. The encoding is ambiguous and lossy-looking — but the real problem is it's used as the sole bearer credential for /contracts/sign/:token with no expiry check visible. Should be crypto.randomUUID() or hex.
S5 — Session in localStorage, no expiry. src/context/SupabaseAuthContext.tsx:97 writes haloLocalAuthSession plus userRole/userRoles as separate localStorage keys. XSS-readable; role is client-writable, and since the backend never checks (S1), editing localStorage.userRole to "Admin" is a complete privilege escalation.
S6 — Vestigial Auth0 with hardcoded credentials. src/main.tsx:13-14 — real tenant domain and client ID as literal fallbacks. Nothing calls useAuth0. Dead code shipping live credentials.
S7 — Dual-backend split-brain. 43 files import the Supabase client. Concentrated in src/components/payroll (15 files) — where a finished Convex replacement sits unused. src/services/creator (5), src/pages (5). With VITE_DISABLE_SUPABASE=true these fail per-call silently rather than at boot, so the payroll page degrades invisibly.
S8 — Two permission systems. convex/rolePermissions.ts:4 (canonical ladder, 10 roles) vs src/utils/permissionUtils.ts:11. Note CLAUDE.md is now stale here — src/utils/permissionUtils.ts:46 reads api.rolePermissions.list, i.e. it has already migrated off Supabase. The duplication is now logic-level, not backend-level.
S9 — Type safety defeated at the boundary. 105 (api as any) casts erase Convex's end-to-end typing — its main selling point. 511 total any.
S10 — Zero tests. No framework, no test files, 80k LOC handling payroll, invoicing, and credentials.
S11 — Broken messaging. src/pages/Messages.tsx:22 uses process.env in a Vite client bundle — always undefined.
S12 — 159 console.log in production, including a role-dump at src/pages/Payroll.tsx:33-41.
5. What is genuinely good
- CLAUDE.md itself. Rare quality: it documents rationale and failure modes, not structure — "provisioning creates duplicates alongside the originals", "which has already happened once, breaking Model Profile's announcements". Preserve verbatim.
convex/driveStructure.tssource-of-truth discipline. Deliberately Convex-free (:9-12) so backend and UI import one definition.USED_FOLDER = "USED : POSTED"carries the exact spelling gotcha inline (:16-21). Textbook.markUsedordering rationale (convex/driveContent.ts) — Drive move before index delete, reasoned from data-loss consequence.- Index-holds-unused-only +
lastFileAddedAtas a monotonic high-water mark — a genuinely subtle correctness insight (deriving it would make a fully-posted creator look inactive). - Alert thresholds computed, never stored (
convex/driveIndex.ts) — turns a migration into a constant edit. - Scheduled, never-awaited provisioning — a Drive outage can't fail an onboarding. Correct decoupling.
convex/currency.ts:5-15— server-side key with the reason stated, plus per-day caching against quota exhaustion.convex/payroll.tsdocblock — explains the week-scoped query design vs the Supabase version's fetch-everything-and-filter. The best-engineered module in the repo; it just needs wiring.RouteErrorBoundary×useRouteMemoryinteraction — the team noticed that route memory turns a mount-crash into a permanent trap, and made "Back to dashboard" clear the key. Systems-level thinking.vite.config.tsiCloud watcher tuning — pragmatic fix for a real environment problem.
Recommended sequence: (1) Convex ctx.auth on every function — nothing else matters until this lands; (2) replace the vault with Vaultwarden; (3) swap haloAuth for Convex Auth; (4) wire convex/payroll.ts and delete 15 Supabase payroll files; (5) then the commodity swaps in §3.