How it runs
thybrid Worker as the public site. middleware.ts rewrites any request on host admin.thybrid.com to /admin-v2/*, adds a per-request CSP nonce, and refuses cross-site admin API calls. Old thybrid.com/admin/* answers 308 to the admin host. Cloudflare Access sits in front. The dev host never serves admin.
flowchart LR
U["Team member"] --> ACC["Cloudflare Access (email PIN)"]
ACC --> W["Worker thybrid: /admin-v2 pages + server actions"]
W --> AUTH["Sign-in: password + 2-step code"]
W --> DDB[("DynamoDB: applications, admin-users, audit")]
W --> NEON[("Neon CRM: about 92 tables")]
W --> KV[("KV: switches, throttles")]
W --> EXT["Slack, Zoom, Stripe, Resend, Meta, WhatsApp, Notion, OpenRouter"]
SW["thybrid-sweep (crons)"] -->|"Bearer CRON_SECRET"| W
EXT -->|"webhooks"| W
MCP["thybrid-mcp (read-only)"] --> NEON
CRM["thybrid-crm-sync"] --> NEON
UI: app/admin-v2
Shell is AdminShell.tsx (sidebar, account menu, phone tab bar) with a role-gated nav in nav-model.ts, tested against ROLES.md.
| Area | Routes | What it does |
|---|---|---|
| Home | / | KPIs, ads, social, funnel viz, filters. |
| Inbox | /inbox, /inbox/contact/[id] | Unified conversations: email, WhatsApp, Instagram and Facebook DMs. |
| People | /people, /applicant/[id] | Filterable table, saved lists, bulk actions, three-column record with thread, notes and call card. |
| Pipeline | /pipeline | Kanban mirroring the old GHL stages. |
| Calendar | /calendar, /calendar/book | Booked calls, hours editor, blocked times, Google busy link, admin book-a-call. |
| Calls | /calls, /calls/[id] | Zoom recording, transcript, AI summary and score. |
| Reviews | /reviews | Applications waiting accept or decline (human flow). |
| Tasks | /tasks | My, Today, Overdue, All, Done. |
| Campaigns and Marketing | /campaigns, /marketing/* | Email campaigns and segments, social composer and planner, media library, series, CSV import, comments inbox, analytics, brand. |
| Payments | /payments, /payments/unmatched | Invoices, payment links, unmatched Stripe payments. |
| Reports | /reports/{ads,attribution,cancellations,hosts}, /digest | Owner and admin reports, weekly digest. |
| Automations, Ask, Exports | /automations, /ask, /exports | Plain English to journey rules, read-only Q and A with sources, xlsx and csv exports. |
| Connections | /connections/* | Per-person Slack, accounts, API keys, provider apps, data sync, WhatsApp coexistence. |
| Team, Audit, Sessions | /team, /audit, /sessions | Users and roles, audit log, sessions. |
| Sign-in | /sign-in, /enroll/[token] | Password and two-step enrol by one-time reset link. |
Server actions (all through requireUser()): applicant, applicant-task, book, call, call-hold, connections, api-keys, final-draft, google, hours, invoice, list, people, people-bulk, privacy (erase and SAR), review, tasks, team, wa-prince.
Auth, roles, security
- Sign-in: own accounts, no shared password. Cookie
thybrid_admin_v2(host-only, HttpOnly, Secure, SameSite Lax) with a server-side session row. 8 hours idle, 7 days maximum. PBKDF2 password hash, RFC 6238 two-step code. Users live in DynamoDB. - Throttle: 5 tries per account and IP, 20 per IP, per 15 minutes, counters in KV with hashed keys.
- Cloudflare Access: email PIN in front of the admin host since 16 Oct.
/api/*is never gated so webhooks work. The code does not verify the Access token itself. - Roles: owner, admin, closer, coach, plus Marketing outside the rank. Fine-grained permission strings. A test fails if
permissions.tsand ROLES.md diverge. - Machine access: cron and queue doors use a
CRON_SECRETbearer. Webhooks verify provider signatures. The MCP Worker has its own Access check. - Privacy: erase and subject access requests, tombstones, suppressions. Audit log for every action.
API routes
| Group | Routes | Guard |
|---|---|---|
| Admin | api/admin/*: ai, audit, broadcasts, segments, conversations, connections, google, marketing, people, privacy, slack-delete, rec | Admin session |
| Provider webhooks | stripe, slack/interact, zoom, whatsapp, meta/dm, email/inbound, ses/bounce, marketing/bounce and unsubscribe, call-bot | Provider signature |
| Cron doors | api/cron/*, 24 routes | Bearer secret |
| Other | call-transcript (token page), reports/build, v1/health (API key) | Token, secret, key |
Logic: app/lib domains
crm*.ts and pg.ts (Neon HTTP client), app/db.ts (DynamoDB).Database: Neon tables by migration
| Group | Tables |
|---|---|
| Core CRM (0001) | users, contacts, pipelines, stages, opportunities, opportunity_history, calendars, calendar_slots, appointments, tags, notes, custom_fields, audit_log |
| Conversations (0002, 0014, 0028) | conversations, messages, templates, quick_replies, conversation_events, conversation_reads, contact_identities |
| Broadcasts and email (0003, 0026) | segments, broadcasts, broadcast_recipients, ab_variants, unsubscribes, email_suppressions, soft_bounces, email_templates, email_settings |
| Privacy (0004, 0016) | consents, dsr_requests, suppressions, tombstones |
| GHL exit mirror (0005 to 0009) | ghl_import_excluded, ghl_users, mirror upsert functions. Historical. |
| Native booking (0010 to 0015, 0033) | booking_schedules, booking_settings, blocked_times, google_calendar_connections, shadow logs |
| Connections (0018, 0027) | connections, connection_secrets, connection_apps, connection_grants, connection_events, connection_quota |
| Social (0019 to 0024) | about 30 tables: accounts, campaigns, variants, media, uploads, events, metrics, followers, inbox, snippets, series, short_links |
| Exports, sync, Ask (0025) | exports, export_downloads, sync_definitions, sync_records, sync_runs, ask_questions, ask_spend |
| Marketing hub (0029) | marketing_ad_daily, marketing_funnel_daily, marketing_collector_runs |
| Automations, views, tasks (0030 to 0032) | automations, automation_enrolments, automation_runs, people_views, team_tasks |
| Pending | 0003_rls_phase2: row-level security. Not applied. The Worker connects as an owner role that bypasses it. |
DynamoDB tables used by the OS: thybrid-applications (shared), thybrid-admin-users, thybrid-admin-sessions, thybrid-audit-log, thybrid-ai-budget, thybrid-ads-daily.
Integrations and who owns them
| Service | Owning code |
|---|---|
| Slack | app/slack.ts (all cards), personal-notify/*, api/slack/interact, ops-incident.ts |
| Stripe | stripe*.ts, invoices.ts, payment-write.ts, api/stripe/webhook |
| Zoom | zoom-native/* (one server-to-server app per host), booking-native/zoom.ts, api/zoom/webhook |
| Resend and SES | email*.ts, email-campaigns/send.ts, bounce routes |
| Meta | meta-capi.ts, social-dm*.ts, api/meta/dm/webhook, social/networks/* |
| WhatsApp Cloud API | wa-*.ts. Built, switched off. Humans send from their phones. |
booking-native/google-*.ts (Calendar busy), google-ads.ts | |
| Notion | notion-cc.ts, command-centre/notion-io.ts |
| AI | ai/*, llm.ts, modal-llm.ts (relay to OpenRouter). Only Authorization and Content-Type headers. |
| AWS | DynamoDB via app/db.ts, S3 via s3.ts (transcripts, media), R2 for social media and CRM snapshots |
Scheduled work (UTC; Bangkok is +7)
| When | Route | Does |
|---|---|---|
| every 5 min | sweep | Reminders, expiry, WhatsApp delivery, digests at their hour, call-hold release. Also ticks journeys, broadcasts, social, connections, data sync. |
| hourly :17, :33, :43 | crm-sync, invariants, health | Stage poll, data-truth checks, integration probes. |
| :09 :24 :39 :54 and :21 :51 | call-bot-schedule, call-bot-reconcile | Recorder scheduling. Recorder is off on prod. |
| every 5 min, offset | zoom-native-poll | Recordings and transcripts. |
| daily | reports/build, ad-alerts, funnel-digest, meta-digest, signal-card, daily-scoreboard, journey-shadow-compare | Dashboard rebuild and digest cards. |
| Monday | weekly-review, adset-scoreboard, weekly-digest | Weekly business review and scoreboards. |
| other Workers | crm-sync 00:41, jobs 02:15 / 02:30 / 02:45 | Cold backup, Google conversions, Clarity, collectors. |
Switches the OS reads
Live switches sit in one KV namespace: settings:apply_flow:* (now human), settings:lp_arms:*, host-blackout:*, API key and rate-limit keys, AI and sync counters. Mode variables in wrangler.jsonc include JOURNEY_ENGINE (shadow), CALL_BOT_MODE (off), WA_PROVIDER (off), SOCIAL_POST, EMAIL_CAMPAIGNS (shadow), PERSONAL_NOTIFY, CONNECTIONS_HUB, EXPORTS, DATA_SYNC, ASK, AI_FEATURES. The Worker is at Cloudflare's 128 text-binding cap, so new settings go in code or KV.
Where it is going
The Build Plan has nine phases: stabilise, data foundation, multi-tenant core (tenant ids, row-level security, DynamoDB into Postgres), reliability, agent platform, pilots, marketing and content agents, domains and onboarding, bookings and rooms, B-roll connector. The Product Overview lists 19 features. About 11 exist in code today. Multi-tenancy is easiest after the repo split.