Engineering & Ops Reference Β· v1.0 Β· 2026-04-23

BSP Ecosystem + Handshake Reference

How Bright Side Plumbing's digital ecosystem is wired together β€” what runs where, how pieces talk to each other, and what happens when a customer request comes in. Primary audience: Stephanie, Kalen, Tim, and any future team member needing to orient to the stack.

Table of contents

  1. Executive summary
  2. πŸ‘₯ The cast of systems
  3. πŸ”— The handshake systems (primary focus)
  4. πŸ“Š End-to-end scenarios
  5. πŸ” Credentials + secrets map
  6. 🚨 Common failure modes + playbooks
  7. πŸ—“οΈ Lifecycle + ownership map
  8. 🌐 URLs + access map
  9. πŸ“ What's changing soon
  10. Glossary

1. Executive summary

BSP runs a three-ring digital ecosystem. The outer ring is what customers touch: the website (Bricks Builder on WordPress), phone calls (3CX PBX), SMS (Telnyx), and the AI voice agent Daniel (Vapi). The middle ring orchestrates: the Nexus VM runs Python pipelines (Titan) that pull data from external systems, generate page content, route messages, and schedule work. The inner ring holds data: ServiceTitan is the source of truth for jobs and customers; Google Business Profile is the source for reviews; LiteSpeed + Cloudflare cache the edge. Claude Code agents (like this one) sit outside the rings β€” they use SSH + REST to ship changes.

The whole thing is held together by handshakes β€” the well-defined points where one system hands data or control to another. When those handshakes work, customers get same-day service with confirmation SMS within 60 seconds of booking. When they break (like the Apr 20 Tim Mahony incident β€” ServiceTitan booking auto-dismissed + our form submission threw 400), customers almost churn. This document catalogs every handshake so the next breakage is easy to diagnose.

                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       β”‚   CUSTOMER-FACING SURFACE (outer) β”‚
                       β”‚   🌐 website  πŸ“ž phone  πŸ’¬ sms     β”‚
                       β”‚       πŸ€– Daniel AI voice          β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                 handshakes
                                      β”‚
                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       β”‚        ORCHESTRATION (middle)      β”‚
                       β”‚  Nexus VM Β· Titan pipelines Β· WP   β”‚
                       β”‚  cron Β· Claude Code agents          β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                 handshakes
                                      β”‚
                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       β”‚      DATA + EDGE (inner)           β”‚
                       β”‚  ServiceTitan Β· GBP Β· Telnyx Β· 3CXβ”‚
                       β”‚  LiteSpeed Β· Cloudflare Β· Postgres β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. πŸ‘₯ The cast of systems

Each card: what the system is, what it does, where it lives, who owns it, why it matters.

πŸ–₯️ Nexus VM

The orchestration host. Ubuntu server running all Python pipelines, custom APIs, cron jobs, and integration wrappers.

  • Runs Titan pipelines (location_page_mining.py, fleet_availability.py, etc.)
  • Hosts Nexus dashboard + Ashton dashboard on port 8501
  • Home of /opt/nexus/ codebase

Where: dovew@34.55.179.122 (GCP)
Owner: Robert. SSH key ~/.ssh/google_compute_engine
Timezone: America/Chicago

βš™οΈ Titan pipelines

Python scripts that generate content, pull data, run recurring work. Live under /opt/nexus/titan/.

  • location_page_mining.py β€” generates city page briefs from ST + GBP + Fleet data
  • fleet_availability.py β€” every 15 min, computes per-city tech ETA via Google Distance Matrix (Rung 1: ST schedule-based, no GPS)
  • nexus_html_logger.py β€” writes MH entries
  • titan_sync_daemon.py β€” mirrors ServiceTitan data to local Postgres

Where: /opt/nexus/titan/
Owner: Robert (development) Β· Nexus VM cron (execution)

🌐 Bricks Builder

WordPress theme that builds pages visually. Replaces Oxygen (scheduled for Mon/Tue Apr 27-28 2026 production cutover).

  • Version 2.3.2 on staging Β· 2.3.3 available
  • Stores page content in _bricks_page_content_2 post meta (PHP-serialized array)
  • CSS loading mode: inline (staging)
  • 62 Global Classes currently defined

Where: staging bricks.callbrightside.com Β· prod (post-cutover) callbrightside.com
Owner: Robert (license + domain) Β· Claude Code (page builds via REST)

πŸ“¦ Code Snippets plugin

WordPress plugin that injects custom PHP/CSS/JS at runtime hooks (wp_head, wp_footer, init). Our entire polish stack lives here.

  • Authenticated REST API at /wp-json/code-snippets/v1/snippets
  • 79 active snippets site-wide Β· 7 active for OP 258 after Phase D consolidation
  • Quirk: PUT on active field inconsistent across plugin versions

Where: WordPress wp_snippets table
Owner: Claude Code (runtime) Β· Robert (approval for production ships)

πŸ”§ ServiceTitan

SaaS CRM/dispatch system. Source of truth for customers, jobs, bookings, invoices, technician schedule.

  • Tenant ID 4316907157
  • API base api.servicetitan.io with /crm/v2, /jpm/v2, /dispatch/v2, /telecom/v2, /settings/v2 paths
  • Scheduling Pro widget = first-touch online booking (has a 73% Dismissed-queue rate β€” known issue)

Where: ServiceTitan SaaS (URL TBD β€” ask Robert for login URL)
Owner: Ashton (operational) Β· Robert (API credentials)

⭐ Google Business Profile (GBP)

Google's local-business listing + reviews source. BSP's 4.9β˜… rating visible on Maps + Search comes from here.

  • Live rating 4.9, review count 392+ (as of Apr 23 2026 prod page title)
  • Internal wrapper API at Nexus: /api/gbp/reviews, /api/gbp/reviews/recent, /api/gbp/reviews/stats
  • External API: mybusiness.googleapis.com

Where: Google infrastructure (account: BSP)
Owner: Robert (account) Β· Ashton (review responses)

πŸ’¬ Telnyx

Programmable SMS provider. Replaces prior Twilio consideration for customer-facing and internal alert messaging.

  • 10DLC brand + campaign registered for TCPA compliance
  • Messaging profile + BSP-owned FROM number
  • Future wrapper at /opt/nexus/titan/integrations/telnyx_sms.py

Where: Telnyx SaaS + Nexus wrapper
Owner: Robert (account) Β· Tim (incoming implementation for dismissed-queue worker)

πŸ“ž 3CX

Voice PBX running BSP phone lines. VoIP routing + extension management + call control API.

  • Managed by LawnPhone.com (David Kite / Brandon Lofthouse Β· support@lawnphone.com)
  • API keys present in .env (config + admin keys separate)
  • Office hours corrected to 8am start per MH bsp-apr18-3cx-officehours-8am-correction

Where: 3CX instance URL TBD β€” in LawnPhone.com-managed env
Owner: Robert (account, approves changes) Β· Ashton (daily operations) Β· LawnPhone.com (technical admin)

πŸ€– Daniel (Vapi AI agent)

AI voice assistant for inbound/outbound calls. Handles scheduling prompts, FAQ, and can trigger ServiceTitan booking.

  • Runs on Vapi platform (not Retell β€” Retell deprecated Apr 2026)
  • Assistant ID e2920d04 Β· Phone +19139639817
  • Wrapper at /opt/nexus/titan/api/vapi_voice.py (webhook handler)

Where: Vapi SaaS + Nexus wrapper
Owner: Robert (prompt + flow design) Β· Ashton (takes escalations from Daniel)

πŸ“„ Morpheus

Documentation rendering layer. Serves the playbook HTML files (Codebase Doc, Master History, this doc) under morpheus.callbrightside.com/documents/.

  • Source directory /opt/nexus/nexus/scripts/output/playbooks/ on VM
  • HTML files committed directly β€” no build step

Where: Nexus VM Β· DNS alias

☁️ Cloudflare

Edge CDN + cache in front of callbrightside.com. Also bricks.callbrightside.com via orange-cloud.

  • Cache purged via /client/v4/zones/{id}/purge_cache
  • Zone ID + API token in CLOUDFLARE_ZONE_ID + CLOUDFLARE_API_TOKEN env vars
  • Before Apr 23 bug fix, scripts were looking for CF_API_TOKEN β€” silently failing for months

Where: Cloudflare SaaS
Owner: Robert

⚑ LiteSpeed Cache (LSC)

WordPress-side page cache. Sits between the Bricks render pipeline and Cloudflare edge.

  • Plugin active: litespeed-cache/litespeed-cache.php
  • Custom purge endpoint wrapping it: /wp-json/bsp/v2/cache/purge

Where: WordPress plugin on staging + prod
Owner: Robert

🧠 Claude Code

Development agent (the one writing this doc). SSHs into Nexus VM, executes Python, edits code, ships snippets.

  • Access: SSH key google_compute_engine + WordPress app password claude-api user
  • Guardrails: CLAUDE.md rules 0-8, proof-of-work protocol, one-task-at-a-time, receipts-required
  • Constraints: can't touch production without explicit Robert approval

Where: Claude Code CLI (local) + ephemeral SSH sessions
Owner: Robert (operator)

⏱️ Tim's dismissed-queue worker (planned)

New Python cron/systemd worker to rescue ServiceTitan Dismissed bookings. See /tmp/tim_handoff_plan.md (317 lines, updated Apr 23).

  • Target location: /opt/nexus/titan/workers/dismissed_queue/
  • Cadence: every 15 min, 6am-10pm CT
  • Will use Telnyx SMS + 3CX voice rails

Where: Nexus VM (once shipped)
Owner: Tim (engineer) β€” Robert confirms prereqs before Tim starts

3. πŸ”— The handshake systems

This is the core of this document. Every place where two systems exchange data or control is a handshake β€” the clean, defined boundary between them. Breakages almost always live at handshakes, not inside systems.

Each handshake below lists: direction, auth, protocol, endpoint(s), trigger, payload, purpose, failure mode, retry logic, observability.

πŸ“ž Handshake 1 β€” Customer call ↔ 3CX ↔ ServiceTitan

 Customer phone β†’ 3CX (VoIP) β†’ answered by tech OR voicemail
        β”‚
        β”œβ”€β”€ answered β†’ manual ST entry by Ashton
        └── voicemail β†’ callback flow (70% book next day per MH)
πŸ“€ Direction
Customer β†’ 3CX β†’ (manual) ServiceTitan Β· voicemail β†’ (manual) callback
πŸ” Auth
PSTN β†’ 3CX trunk Β· ST entry via Ashton user session
πŸ“‘ Protocol
SIP (inbound), manual data entry (ST)
πŸ“ Endpoint(s)
BSP published phone (913) 963-1029 Β· 3CX config API if programmatic lookup needed
⚑ Trigger
Customer dials the number
πŸ“¦ Payload
Caller CID, call duration, recording URL (3CX side)
🎯 Purpose
First-touch customer contact β†’ booking or lead
⚠️ Failure mode
Extension loop (Rule 14 Apr 21 incident β€” caller hits dead extension, can't reach anyone). 30% of after-hours voicemails don't convert.
πŸ”„ Retry
Human callback; no automated retry
πŸ“ Observability
3CX call log via THREECX_API_URL; MH bsp-apr20-memory-backfill-3cx-afterhours-routing

Sources: MH apr17-3cx-toggle-retired-permanent-fix, apr18-3cx-officehours-8am-correction, apr20-memory-backfill-3cx-afterhours-routing, apr21 Tim Mahony incident.

πŸ’¬ Handshake 2 β€” Customer SMS ↔ Telnyx ↔ Nexus worker ↔ ServiceTitan

 Customer SMS β†’ Telnyx number β†’ webhook (POST) β†’ /api/telnyx/inbound
        β”‚
        β”œβ”€β”€ STOP/HELP β†’ TCPA opt-out handler
        β”œβ”€β”€ Lead text β†’ ServiceTitan lead creation
        └── Other reply β†’ Slack to Ashton + titan.telnyx_messages log
πŸ“€ Direction
Customer β†’ Telnyx β†’ Nexus β†’ ServiceTitan (outbound: Nexus β†’ Telnyx β†’ Customer)
πŸ” Auth
Telnyx webhook signature (HMAC-SHA256). ST: OAuth2 per tenant credentials
πŸ“‘ Protocol
Inbound webhook (JSON POST) Β· Outbound REST (Telnyx API v2)
πŸ“ Endpoint(s)
Inbound: https://[VM]/api/telnyx/inbound route not yet built Β· Outbound: https://api.telnyx.com/v2/messages
⚑ Trigger
Customer sends SMS to BSP Telnyx number
πŸ“¦ Payload
Inbound: message body, from_number, message_id, direction. Outbound: to, text, messaging_profile_id
🎯 Purpose
2-way SMS: customer inquiries + booking confirmation handshake + dismissed-queue rescue alerts
⚠️ Failure mode
Telnyx retries webhook 3x if our endpoint returns non-2xx. If Telnyx is down (very rare), messages queue on their end.
πŸ”„ Retry
Telnyx built-in webhook retries Β· outbound: exponential backoff if we get 5xx
πŸ“ Observability
Telnyx dashboard Β· titan.telnyx_messages table (once Tim's worker ships)

Sources: Codebase Doc Β§38 SMS+Voice rails Β· Tim handoff plan Apr 23 update.

πŸ“Š Handshake 3 β€” Nexus pipeline ↔ GBP API

 cron (hourly) β†’ fetch_gbp_reviews_for_city(city_slug)
        β†’ Internal /api/gbp/reviews or /api/gbp/reviews/recent
        β†’ (internal wraps) mybusiness.googleapis.com
        β†’ returns reviews[] with author, rating, text, date
        β†’ pipeline filters by city + landmarks mention
πŸ“€ Direction
Pipeline β†’ internal GBP wrapper β†’ Google My Business API
πŸ” Auth
OAuth2 access token stored on Nexus for BSP's GBP account
πŸ“‘ Protocol
REST (JSON)
πŸ“ Endpoint(s)
Internal: /api/gbp/reviews/recent?limit=400 Β· External: mybusiness.googleapis.com
⚑ Trigger
Pipeline runs (location_page_mining.py, nexus_doc_refresh.py, standup_autobuild.py)
πŸ“¦ Payload
Request: limit, optional filters. Response: review objects with star rating, text, author, date, mentioned landmarks/cities
🎯 Purpose
Populate location page reviews section with real customer voices + mention detection
⚠️ Failure mode
Token expiry silently fails. API quotas (very high for GBP but finite).
πŸ”„ Retry
Pipeline falls back to cached review_intelligence.db on failure
πŸ“ Observability
Nexus logs Β· local SQLite review_intelligence.db

Sources: /opt/nexus/titan/api/gbp.py Β· /opt/nexus/titan/location_page_mining.py.

πŸ—“οΈ Handshake 4 β€” Nexus pipeline ↔ ServiceTitan API

 Multiple callers (titan_sync_daemon, eta_relay, auto_dispatcher, referral_weapon, money_finder)
        β†’ api.servicetitan.io/{crm|jpm|dispatch|telecom|settings}/v2/tenant/4316907157/...
        β†’ OAuth2 bearer token per-tenant
        β†’ returns jobs, customers, dispatch, calls
πŸ“€ Direction
Pipeline β†’ ServiceTitan (bidirectional β€” we read jobs/customers, we write leads/campaigns)
πŸ” Auth
ServiceTitan OAuth2 (client credentials grant) Β· tenant-specific headers
πŸ“‘ Protocol
REST (JSON)
πŸ“ Endpoint(s)
api.servicetitan.io/crm/v2/tenant/4316907157/leads (create lead β€” see Snippet #97 campaignId fix) Β· /jpm/v2/tenant/.../jobs (read jobs) Β· /telecom/v2/tenant/.../calls (call history) Β· /dispatch/v2/tenant/.../... (fleet dispatch) Β· /settings/v2/tenant/... (config)
⚑ Trigger
Cron (every 15 min: fleet_availability) Β· event (contact form submit β†’ lead create) Β· daily (titan_sync_daemon)
πŸ“¦ Payload
Varies per endpoint. Lead creation requires campaignId, businessUnitId, jobTypeId, customer object (learned via Apr 21 Tim Mahony incident).
🎯 Purpose
Sync customer + job data into local Postgres Β· create leads from web forms Β· measure funnel health
⚠️ Failure mode
Missing campaignId β†’ 400 Bad Request (exact bug Apr 20). Token expiry β†’ 401. Rate limits under heavy pipeline runs.
πŸ”„ Retry
Exponential backoff on 5xx; token refresh on 401
πŸ“ Observability
titan.jobs, titan.customers tables Β· Nexus logs

Sources: /opt/nexus/titan/api/*.py Β· MH bsp-apr20-contact-form-pipeline-full-fix Β· MH bsp-apr21-tim-mahony-incident-snippet97.

🌐 Handshake 5 β€” Nexus pipeline ↔ WordPress REST API

 Pipeline β†’ /wp-json/wp/v2/pages/{id}                  (GET/POST/PUT)
         β†’ /wp-json/code-snippets/v1/snippets          (GET/POST/PUT/DELETE)
         β†’ /wp-json/bsp/v2/cache/purge                 (POST)
         β†’ Basic auth with claude-api application pw
πŸ“€ Direction
Pipeline β†’ WordPress (read + write page content, manage snippets)
πŸ” Auth
Basic auth with Application Password (claude-api user Β· password in .env as BRICKS_WP_APP_PASSWORD)
πŸ“‘ Protocol
REST (JSON)
πŸ“ Endpoint(s)
See diagram above. Namespace discovery at /wp-json/ lists: wp/v2, code-snippets/v1, bricks/v1, bricks-ai-studio/v1, litespeed/v1-v3, bsp/v1-v3, hostinger-*, mcp.
⚑ Trigger
Pipeline run (page regeneration) Β· one-shot ops (consolidation, cache purge)
πŸ“¦ Payload
Pages: title, content, meta. Snippets: name, code, scope, priority, active.
🎯 Purpose
Page content generation Β· snippet lifecycle (create, activate, deactivate, delete) Β· cache invalidation
⚠️ Failure mode
update_post_meta returns false on success (WP internal hash-compare false positive; Apr 23 discovery) β€” use $wpdb->update fallback Β· PUT on snippet active field inconsistent across Code Snippets plugin versions
πŸ”„ Retry
Idempotent operations (PUT) Β· DELETE + POST if PUT active flag doesn't persist
πŸ“ Observability
Nexus logs Β· WP debug.log Β· Code Snippets admin UI

Sources: Codebase Doc Β§28.2 update_post_meta false Β· Β§28.1 PUT active inconsistent.

πŸ“¦ Handshake 6 β€” WordPress ↔ Code Snippets plugin ↔ rendered page

 HTTP request β†’ WP bootstrap β†’ init action β†’ snippets load
        β”‚
        β”œβ”€β”€ wp_head hook: CSS snippets inject into 
        β”œβ”€β”€ wp_footer priority 99999: CSS/JS snippets inject before 
        β”œβ”€β”€ template_redirect priority 1: ob_start snippets rewrite HTML
        └── Bricks renders its own content_2 meta into main
πŸ“€ Direction
Plugin β†’ WP render pipeline β†’ final HTML output
πŸ” Auth
None (runs server-side as WP)
πŸ“‘ Protocol
PHP action/filter hooks
πŸ“ Endpoint(s)
Hook: wp_footer priority 99999 (our standard) Β· template_redirect priority 1 (server-side img src swap) Β· init (one-shot mutations)
⚑ Trigger
Any page load Β· admin request Β· REST call
πŸ“¦ Payload
PHP source code stored in wp_snippets table Β· executed in-process
🎯 Purpose
Runtime code injection without touching theme files (best-practice per Bricks Academy)
⚠️ Failure mode
PHP fatal errors β†’ snippet auto-deactivates (Code Snippets plugin safety) Β· infinite loops β†’ admin hangs until execution limit
πŸ”„ Retry
None β€” failed snippet is inactive until fixed
πŸ“ Observability
WP debug.log Β· Code Snippets admin Error log panel

Sources: Codebase Doc Β§27.5 Custom Code integration Β· Academy lesson 01_custom_code.md.

🎨 Handshake 7 β€” Bricks Builder ↔ WordPress post meta

 Builder save β†’ write to post_meta._bricks_page_content_2 (PHP-serialized array)
        β†’ Bricks 2.3.2 quirk: some 4-value properties serialize as literal "Array" string
        β†’ On frontend render, Bricks parses meta β†’ emits CSS (inline or external)
πŸ“€ Direction
Builder UI β†’ post meta Β· Render pipeline ← post meta
πŸ” Auth
WP admin session (builder) Β· post meta public-read, admin-write
πŸ“‘ Protocol
PHP direct DB access via get/update_post_meta
πŸ“ Endpoint(s)
Meta keys: _bricks_page_content_2 (~49KB for OP 258), _bricks_template_conditions, _bricks_editor_mode, _bricks_template_type
⚑ Trigger
Builder save Β· Claude Code native-save (mutating tree directly)
πŸ“¦ Payload
PHP-serialized array of element definitions (section β†’ container β†’ block β†’ div hierarchy, each with id/name/parent/children/settings)
🎯 Purpose
Source-of-truth for visual page structure
⚠️ Failure mode
wp_postmeta.meta_value must be LONGTEXT (verified βœ“ on staging). Bricks Array serialization bug for 4-value padding/margin/border-radius.
πŸ”„ Retry
Data is idempotent; failed write can be retried. Use $wpdb->update when update_post_meta no-ops.
πŸ“ Observability
MySQL direct query Β· Bricks builder preview

Sources: Codebase Doc Β§28.2, Β§28.3, Β§37.4 Β· Bricks Academy lesson 10 Known Issues.

🧠 Handshake 8 β€” Claude Code ↔ Nexus VM

 Claude Code (local) ──ssh/scp──▢ Nexus VM (34.55.179.122)
         β”‚
         β”œβ”€β”€ python3 /tmp/script.py (with .env credentials)
         β”œβ”€β”€ curl /wp-json/... (WP REST)
         β”œβ”€β”€ curl /api/cloudflare/zones/{id}/purge_cache
         └── python3 nexus_html_logger.py (MH writes)
πŸ“€ Direction
Claude Code β†’ VM (execute) Β· VM β†’ Claude Code (stdout/stderr captured)
πŸ” Auth
SSH key ~/.ssh/google_compute_engine (public half authorized on VM for user dovew)
πŸ“‘ Protocol
SSH (execute + capture) Β· SCP (file transfer)
πŸ“ Endpoint(s)
dovew@34.55.179.122 port 22
⚑ Trigger
Claude Code chooses to run a command Β· user sends a request requiring VM access
πŸ“¦ Payload
Python scripts, shell commands, output
🎯 Purpose
Execute anything requiring server-side credentials or access to Nexus local state
⚠️ Failure mode
SSH key missing/rotated β†’ connection refused Β· VM down β†’ unreachable Β· network partition
πŸ”„ Retry
No automated retry; Claude Code surfaces failure
πŸ“ Observability
Shell exit codes Β· Claude Code conversation log

⏱️ Handshake 9 β€” Cron ↔ Nexus pipelines

 Linux cron daemon β†’ /usr/bin/python3 /opt/nexus/titan/{script}.py
        β”‚
        β”œβ”€β”€ fleet_availability.py          every 15 min
        β”œβ”€β”€ GA4 full report                daily 8am
        β”œβ”€β”€ financial validator            daily after briefing
        β”œβ”€β”€ review intelligence             hourly
        β”œβ”€β”€ CPL monitor                    daily 3pm
        └── (22+ other automations)
πŸ“€ Direction
cron β†’ Nexus pipelines (no return channel; pipelines log to stdout/stderr and local DB)
πŸ” Auth
User dovew; .env loaded at process start
πŸ“‘ Protocol
Unix cron
πŸ“ Endpoint(s)
crontab -l under dovew
⚑ Trigger
Schedule defined per pipeline
πŸ“¦ Payload
Each pipeline has its own inputs (DB queries, API calls) and outputs (JSON files in /opt/nexus/nexus/scripts/output/, SQLite/Postgres writes, MH entries, Slack messages)
🎯 Purpose
Automation β€” data freshness, monitoring, reporting
⚠️ Failure mode
Silent stderr (many pipelines redirect to /dev/null). Missing .env vars. Stale GCP auth.
πŸ”„ Retry
Next schedule tick; no in-run retry
πŸ“ Observability
/opt/nexus/nexus/scripts/output/*_cron.log files Β· MH entries

☁️ Handshake 10 β€” Nexus ↔ Cloudflare API

 Nexus script β†’ POST https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache
        Body: {"purge_everything": true}
        Auth: Bearer CLOUDFLARE_API_TOKEN
πŸ“€ Direction
Nexus β†’ Cloudflare
πŸ” Auth
API token (Bearer). Env vars: CLOUDFLARE_API_TOKEN + CLOUDFLARE_ZONE_ID.
πŸ“‘ Protocol
REST (JSON POST)
πŸ“ Endpoint(s)
https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache
⚑ Trigger
After any page/snippet change that needs edge invalidation
πŸ“¦ Payload
{"purge_everything": true} or {"files": ["https://..."]} for surgical purge
🎯 Purpose
Ensure edge cache reflects latest origin content
⚠️ Failure mode
Apr 23 discovery: scripts previously checked CF_API_TOKEN (wrong name) β€” env actually uses CLOUDFLARE_API_TOKEN. Purges silently returned no-op. Fixed.
πŸ”„ Retry
Manual re-run if response success: false
πŸ“ Observability
CF dashboard cache analytics Β· response body contains result.id for traceability

Source: Codebase Doc Β§34.1.

⚑ Handshake 11 β€” Nexus ↔ LiteSpeed Cache

πŸ“€ Direction
Nexus β†’ WordPress LSC plugin
πŸ” Auth
WP Basic auth (claude-api)
πŸ“‘ Protocol
REST (POST to custom BSP endpoint wrapping LSC)
πŸ“ Endpoint(s)
/wp-json/bsp/v2/cache/purge (BSP custom route that calls LSC internally)
⚑ Trigger
Same as Cloudflare purge β€” after content change
πŸ“¦ Payload
Empty POST
🎯 Purpose
Invalidate origin-side page cache before CF edge re-fetch
⚠️ Failure mode
LSC plugin disabled/malfunctioning β†’ origin serves stale content. 200 response doesn't always mean purge succeeded β€” verify by checking page x-litespeed-cache header after.

πŸ“„ Handshake 12 β€” Morpheus ↔ Playbook HTML files

πŸ“€ Direction
File on VM β†’ web server β†’ browser
πŸ” Auth
Public (morpheus.callbrightside.com is publicly accessible)
πŸ“‘ Protocol
HTTP(S)
πŸ“ Endpoint(s)
morpheus.callbrightside.com/documents/*.html β†’ serves from /opt/nexus/nexus/scripts/output/playbooks/*.html
⚑ Trigger
Any HTTP request for doc URL
πŸ“¦ Payload
HTML files (Codebase Doc ~1.27MB Β· Master History ~2.6MB Β· this Reference doc ~TBD)
🎯 Purpose
Make institutional knowledge accessible to Robert + team without VM SSH
⚠️ Failure mode
Broken HTML β†’ renders degraded Β· file not written β†’ 404
πŸ“ Observability
web server access log Β· Nexus file system

πŸ€– Handshake 13 β€” Daniel ↔ Vapi ↔ 3CX

 Inbound call β†’ 3CX routes to Daniel extension β†’ Vapi answers
        β”‚
        β”œβ”€β”€ AI conversation (Vapi-managed)
        β”œβ”€β”€ Transfer to human ext β†’ 3CX routes to Ashton
        └── Booking intent β†’ vapi_voice.py webhook β†’ ST lead creation
πŸ“€ Direction
Customer phone β†’ 3CX β†’ Vapi Β· webhook back to Nexus for DB logging + ST lead
πŸ” Auth
Vapi API key Β· 3CX SIP trunk Β· Nexus webhook signature validation
πŸ“‘ Protocol
SIP (voice) Β· webhook (Vapi β†’ Nexus JSON POST)
πŸ“ Endpoint(s)
Daniel phone +19139639817 Β· Vapi assistant e2920d04 Β· Nexus webhook at /opt/nexus/titan/api/vapi_voice.py handler
⚑ Trigger
Customer dials Daniel line OR warm-transfer from human agent
πŸ“¦ Payload
Vapi call events (start/end), transcripts, function calls (booking intent, etc.)
🎯 Purpose
AI-assisted call handling β€” reduces load on human agents for simple booking/FAQ
⚠️ Failure mode
Vapi down β†’ calls go to 3CX voicemail fallback
πŸ“ Observability
Vapi dashboard Β· titan.vapi_calls table via transcript_repo.py

Source: /opt/nexus/titan/api/vapi_voice.py Β· MH apr17-vapi-monitoring-deployed.

πŸ’¬ Handshake 14 β€” Homepage chat widget ↔ Daniel (web β†’ voice handoff)

πŸ“€ Direction
Website chat button β†’ tel:+19139639817 (phone) OR in-browser Vapi widget TBD
πŸ” Auth
n/a (initiates call)
πŸ“‘ Protocol
Browser handles tel: URI
πŸ“ Endpoint(s)
Homepage + city pages: a.brxe-button href="#daniel-chat" currently Β· full Vapi browser widget integration deferred
⚑ Trigger
User clicks "Chat with Daniel our AI Agent" button
🎯 Purpose
Low-friction web β†’ voice handoff for AI-assisted booking
⚠️ Failure mode
Current state: anchor href points at #daniel-chat β€” no JS handler yet. Needs Vapi widget wire-up. pending integration

⚠️ Handshake 15 β€” Website form submission ↔ ServiceTitan (gap)

 Customer submits contact form β†’ WordPress POST handler β†’ Snippet #97 β†’ ST /leads API
        β”‚
        └── (gap): NO positive handshake send-off back to customer
                  Tim (customer) raised this Apr 21: "did my form even go through?"
πŸ“€ Direction
Website form β†’ ST lead creation (Apr 21 Tim incident confirmed this path works after Snippet #97 campaignId fix)
πŸ” Auth
Server-side ST OAuth2 (credentials in .env, not browser-accessible)
πŸ“‘ Protocol
PHP form POST β†’ internal ST OAuth wrapper β†’ ST /crm/v2/leads
πŸ“ Endpoint(s)
Snippet #97 bsp-contact-form-st-lead
⚑ Trigger
Contact form submit
πŸ“¦ Payload
customer object (name, phone, email, address), campaignId (GCLID-based routing), businessUnitId, jobTypeId
🎯 Purpose
Convert form visitors β†’ ServiceTitan leads
⚠️ Failure mode
GAP: no customer-facing confirmation after submit. Tim Mahony wrote "I do not have any confirmation" Apr 21. This is the motivation for the Tim handoff plan Deliverable A (confirmation handshake via Telnyx SMS).
πŸ”„ Retry
Browser submits once; no automatic retry
πŸ“ Observability
Slack delivery Β· email delivery Β· ST leads list Β· NO customer-facing acknowledgement (the gap)

Source: Tim handoff plan Β· MH bsp-apr21-tim-mahony-incident-snippet97.

⏱️ Handshake 16 β€” Tim's dismissed-queue worker ↔ ServiceTitan (planned)

πŸ“€ Direction
Worker β†’ ServiceTitan (read Dismissed bookings) Β· Worker β†’ Telnyx (alert Ashton) Β· Worker β†’ Slack (team visibility) Β· Worker β†’ 3CX (emergency voice trigger)
πŸ” Auth
ST OAuth2 Β· Telnyx API key Β· Slack webhook URL Β· 3CX admin key
πŸ“‘ Protocol
REST polling (*/15 min) Β· Outbound webhooks
πŸ“ Endpoint(s)
GET api.servicetitan.io/crm/v2/tenant/4316907157/bookings?status=Dismissed&source=Online Booking Widget&createdAfter={T-48h}
⚑ Trigger
Cron or systemd timer every 15 min during business hours 6am-10pm CT
πŸ“¦ Payload
Booking objects (status, customer contact, booking_id, source, timestamps)
🎯 Purpose
Rescue dismissed-queue bookings before customer churns (prevents next Tim Mahony incident)
⚠️ Failure mode
Worker dies β†’ bookings silently dismiss Β· ST token expiry Β· Telnyx quota Β· Slack webhook rotation
πŸ”„ Retry
Next tick of cron Β· local titan.dismissed_queue_alerts tracks ack state to avoid double-pinging
πŸ“ Observability
titan.dismissed_queue_alerts Β· daily MH entry bsp-{YYYYMMDD}-dismissed-queue-daily (planned) Β· Slack + SMS receipts

Source: Tim handoff plan Apr 23 update (Deliverable C).

πŸ€– Handshake 17 β€” Daniel ↔ ServiceTitan (planned booking flow)

πŸ“€ Direction
Vapi function call β†’ Nexus webhook β†’ ST lead/booking creation
πŸ” Auth
Same ST OAuth2 as human channels
πŸ“‘ Protocol
Vapi function calling β†’ JSON POST to Nexus
πŸ“ Endpoint(s)
Future: Nexus webhook for Vapi-initiated bookings Β· ST /crm/v2/leads
⚑ Trigger
Customer speaks booking intent during Daniel call
🎯 Purpose
Zero-human booking path: Daniel captures enough info, creates ST lead, optionally asks for ST-side booking confirmation
⚠️ Failure mode
Vapi mis-extracts call params Β· incomplete info β†’ bad lead
πŸ“ Observability
Vapi transcript Β· ST lead source tag

Status: medium-term (Q2-Q3 2026). Not shipped as of Apr 23 2026.

πŸ“¬ Handshake 18 β€” Tim's worker ↔ Slack webhook

πŸ“€ Direction
Worker β†’ Slack
πŸ” Auth
Webhook URL (bearer by URL)
πŸ“‘ Protocol
HTTPS POST
πŸ“ Endpoint(s)
SLACK_WEBHOOK_URL TBD β€” Robert to create #bsp-alerts channel + webhook
⚑ Trigger
Dismissed-queue item aged > 60 min Β· 120-min re-alert Β· 240-min escalation
🎯 Purpose
Team-visible audit trail alongside SMS (which is private DM)

4. πŸ“Š End-to-end scenarios

Scenario A β€” New customer calls BSP

 1. Customer dials (913) 963-1029
 2. 3CX answers, IVR or direct route to tech extension
 3. Tech answers OR voicemail
    a. Answered β†’ human sales flow β†’ ServiceTitan manual job creation by Ashton
    b. Voicemail β†’ callback flow (70% book next day per MH)
 4. Job completed β†’ invoiced in ServiceTitan β†’ HCP mirror to Postgres

Scenario B β€” Customer submits website form (Tim's pain point)

 1. Customer fills contact form on callbrightside.com
 2. WordPress POST handler β†’ Snippet #97 fires
 3. Snippet #97 creates ST lead via /crm/v2/leads (campaignId required since Apr 21)
 4. Slack DM to Ashton + email to Robert
 5. (gap) NO SMS to customer β€” they don't know form went through
 6. Ashton manually follows up hours later
 7. Future (Deliverable A): Telnyx SMS fires within 60s β†’ customer reassured

Scenario C β€” Customer texts BSP

 1. Customer texts BSP's Telnyx number
 2. Telnyx β†’ webhook to /api/telnyx/inbound on Nexus
 3. Nexus parses: STOP/HELP β†’ TCPA handler Β· lead text β†’ ST lead Β· other β†’ Ashton Slack
 4. Auto-reply via Telnyx (Variant 1 first-time OR Variant 2 repeat OR manual)

Scenario D β€” Pipeline regenerates a city page at midnight

 1. cron fires location_page_mining.py --city overland-park
 2. Pipeline pulls:
    - GBP reviews (/api/gbp/reviews/recent)
    - ServiceTitan job/customer stats
    - Fleet availability (precomputed every 15 min)
 3. Pipeline builds brief JSON (h1_variants, subtitle, CTAs, services, etc.)
 4. Writes to WP post meta via REST (or PHP-native on page edit)
 5. Triggers wp_update_post β†’ Bricks regenerates inline CSS
 6. Purges: LiteSpeed (/wp-json/bsp/v2/cache/purge) + Cloudflare (/zones/{id}/purge_cache)
 7. Next request fetches fresh HTML from origin

Scenario E β€” Claude Code ships a snippet update

 1. Robert types prompt to Claude Code
 2. Claude writes Python script locally
 3. scp /script.py dovew@34.55.179.122:/tmp/
 4. ssh dovew@34.55.179.122 "python3 /tmp/script.py"
 5. Script loads .env, constructs payload, POSTs to /wp-json/code-snippets/v1/snippets
 6. New snippet active=true β†’ runs on next page load
 7. Script fires LS + CF purges
 8. Claude Code takes a headless Chrome screenshot to verify
 9. Claude writes MH entry via nexus_html_logger.py

5. πŸ” Credentials + secrets map

Master credential store: /opt/nexus/nexus/config/.env (mode 600, owner dovew). Not committed to git.

CredentialEnv varUsed byOwner / rotation
ST OAuth2 client ID/secretST_* varsTitan pipelines (titan_sync, eta_relay, referral_weapon)Robert Β· per ST client policy
GBP OAuth2 tokenGBP_* varsgbp.py wrapperRobert Β· refresh per Google expiry
WP application passwordBRICKS_WP_APP_PASSWORDClaude Code snippet operationsRobert Β· on-demand rotation
Cloudflare API tokenCLOUDFLARE_API_TOKENCache purgeRobert Β· zone-scoped
Cloudflare zone IDCLOUDFLARE_ZONE_IDCache purgeRobert (static)
Telnyx API keyTELNYX_API_KEYFuture worker + SMS handshakeRobert
Telnyx FROM numberTELNYX_FROM_NUMBEROutbound SMSRobert
Telnyx 10DLC brand + campaignTELNYX_10DLC_*TCPA registrationRobert
3CX API URL + keysTHREECX_API_URL, THREECX_API_KEY, THREECX_ADMIN_API_KEYVoice integrationRobert (via LawnPhone.com)
Vapi API key + assistant IDVAPI_API_KEY, VAPI_ASSISTANT_IDDaniel voice agentRobert
SSH private key (Claude β†’ VM)n/a β€” file ~/.ssh/google_compute_engineClaude Code SSHRobert laptop Β· rotation per GCP guidance
Slack webhook URLSLACK_WEBHOOK_URL TBDTeam alertsRobert

6. 🚨 Common failure modes + playbooks

CF cache purge silently fails

Symptoms: Content changes on origin, but edge serves stale content even after "CF PURGE" log line.

Root cause (Apr 23 discovery): Scripts checked env var CF_API_TOKEN; actual var is CLOUDFLARE_API_TOKEN. Conditional fell through, no HTTP call made.

Fix: Use env.get('CLOUDFLARE_API_TOKEN') or env.get('CF_API_TOKEN') (both-compatible). Verify by checking response result.id.

update_post_meta returns false on success

Symptoms: PHP says write failed, but data IS actually updated in DB.

Root cause: WP's internal hash-compare false positive on deeply-nested arrays.

Fix: Fall back to $wpdb->update($wpdb->postmeta, [...], [...]). Pair with wp_update_post(['ID' => $id]) to bump modified time + trigger Bricks regen.

PUT on inactive snippets silently fails to persist active

Symptoms: PUT {active: true} returns 200, subsequent GET still shows active: false.

Fix: DELETE + POST new snippet with target active state. Or use PUT on code field only (which does persist reliably).

Bricks 2.3.2 "Array" serialization bug

Symptoms: CSS output contains padding-top: Array; instead of valid value. Browser ignores.

Fix: Override via custom CSS snippet with explicit values. Possibly resolved in 2.3.3 "Fix DB" tool.

Cascade specificity wars with locked snippets

Symptoms: Your new rule doesn't apply despite being later in source order.

Diagnosis: Inspect which selector has higher specificity. #115 often has (0,1,3,1) patterns.

Fix: Use 3-ID selector chain for a (0,3,0,0) override that beats on pure ID count. In the future, use @layer bricks.reset β€” but cascade layers not currently active on our site (verified Apr 23).

Fleet endpoint returns null after hours

Symptoms: Chip shows no ETA; pipeline uses fallback value.

Expected: Fleet data is only populated during business hours 7am-6pm CT when techs have scheduled jobs. Weekend + holidays = null.

Fix: Chip uses 2-state logic (Snippet #161) β€” "Open Now" during business hours, "Open for Emergencies" otherwise. No fallback number exposed to customer.

7. πŸ—“οΈ Lifecycle + ownership map

PersonDomainAuthority
Robert Dove (you)Product Β· launch timing Β· brand Β· credentials Β· Phase D/F/G decisionsFinal say on ship-or-not
Kalen Barker (4th-gen master plumber)Operational decisions Β· pricing Β· service promises Β· chip copy Β· customer-facing claimsVeto on anything that touches customer experience
Stephanie BarkerBusiness ops Β· hiring Β· financial oversight
full scope TBD β€” confirm with Robert
P/I/S/D/N format communication standard is hers
Ashton King (he)CX Β· ServiceTitan operational Β· phone ops Β· 100 Year Plumbing dutiesDaily booking flow, review response
Audrey GrantDesign (Figma source of truth) Β· service page content playbookVisual design decisions Β· no emojis Β· project-by-project
Tim (engineer β€” TBD full name)Dismissed-queue worker (Deliverable C)Owns implementation of Tim handoff plan deliverables, pending Robert's prereqs
LawnPhone.com (David Kite / Brandon Lofthouse)3CX administrationVendor β€” manages 3CX instance
Claude CodeImplementation execution Β· documentation Β· analysisZero authority β€” requires Robert approval before production action

8. 🌐 URLs + access map

ResourceURLAccess
Production sitehttps://callbrightside.com/Public Β· Oxygen theme currently
Staging sitehttps://bricks.callbrightside.com/Public (noindex)
Morpheus docshttps://morpheus.callbrightside.com/documents/Public
Codebase Doc.../BSP_Bricks_Codebase_Documentation.htmlPublic
Master History.../BSP_Master_Session_History.htmlPublic
This reference.../BSP_Ecosystem_Handshake_Reference.htmlPublic
Nexus VMdovew@34.55.179.122 :22SSH key required
ServiceTitantenant URL TBD Β· API base api.servicetitan.ioAshton (UI) Β· OAuth2 (API)
3CX instanceURL TBD β€” ask LawnPhone.comAdmin creds
Telnyx dashboardhttps://portal.telnyx.com/Robert account
Vapi dashboardhttps://vapi.ai/Robert account
Cloudflare dashboardhttps://dash.cloudflare.com/Robert account
Google Business ProfileManaged via business.google.comRobert account Β· Ashton user
Hostinger (WP host)https://hpanel.hostinger.com/Robert account

9. πŸ“ What's changing soon

ChangeOwnerTargetStatus
Monday/Tuesday production launch β€” deactivate Oxygen, activate BricksRobertApr 27-28 2026Prep in progress
Tim's dismissed-queue worker (Deliverable C)TimPost-launchBlocked on Robert prereqs
Native Bricks header template (Phase G)Claude CodePre-launch recommendedG0 context done; awaiting Robert go
13 remaining city pages (Friday clone batch)Claude Code via pipelinePre-launchPipeline ready; bulk generation deferred
Β§32 Tier 1 data-accuracy strip (4.9/384+/15-sec/60-min)Claude CodePre-launchReady to ship on Robert go
Daniel booking flow (ST integration)Robert + Daniel prompt designQ2-Q3 2026Medium-term
Fleet API real-time integration (beyond schedule-based)RobertQ3-Q4 2026Long-term
Form submission β†’ SMS handshake (Deliverable A)TimWeek after Deliverable CPlanned; addresses Tim Mahony pain point

10. Glossary

Bricks / Bricks Builder
WordPress theme + visual builder. Currently 2.3.2 on staging.
Snippet
PHP/CSS/JS code stored in Code Snippets plugin's wp_snippets DB table, executed via WP hooks.
Template 105
Current header template ("Audrey Header v3") on staging. Scheduled for replacement in Phase G.
Global Class (Bricks)
Reusable CSS class defined in Bricks builder, stored in wp_options.bricks_global_classes.
Theme Styles (Bricks)
Site-wide design tokens (colors, fonts, spacing) with conditions. Stored in wp_options.bricks_theme_styles.
Cascade layers
CSS feature (@layer) that manages priority by layer order instead of specificity. Bricks 2.0+ wraps defaults in @layer bricks β€” but we verified this NOT active on our render output (Apr 23).
Post meta / _bricks_page_content_2
WordPress database record (serialized PHP array) storing Bricks page element tree.
wp_footer / wp_head hooks
PHP action points where injected code runs during page render.
Specificity (CSS)
Rule-weight calculation: (inline, IDs, classes/attrs, elements). Higher specificity wins on same !important status.
Native-save
BSP/Claude term for editing Bricks post meta directly (vs. going through the builder UI) β€” used for bulk content mutations.
Handshake
In this doc: any point where two systems exchange control or data. Boundary where most breakages occur.
Fleet ETA
Drive time in minutes from nearest tech's scheduled job address to a target city, via Google Distance Matrix. NOT real GPS.
Dismissed queue
ServiceTitan Scheduling Pro bookings that auto-land in "Dismissed" status and require human rescue. 73% of April bookings landed here β€” the problem Tim's worker fixes.
Deliverable A/B/C
From Tim handoff plan: A=customer confirmation handshake SMS; B=Booking→Dispatch SOP doc; C=dismissed-queue cron worker. Ship order C→A→B.
Sacred HTML v2
Robert's term for data_weapons_plan_v2.html (NOT BSP_Sacred_HTML_v2.html).
Oxygen
Predecessor page builder on production callbrightside.com. Scheduled for deactivation Mon/Tue Apr 27-28 2026.
LiteSpeed / Cloudflare
Origin page cache (LSC plugin) and edge CDN cache (Cloudflare) β€” purged in that order on content changes.
MH
Master History β€” rolling HTML log at BSP_Master_Session_History.html. Load-bearing for Zeus RAG retrieval between Claude Code sessions.
Zeus RAG
Retrieval-augmented generation layer (18,380 chunks from MH + Sacred HTML etc.) used to surface prior institutional knowledge.
Sacred v2 / Stephanie format
Communication framework: Problem β†’ Impact β†’ Solution β†’ Data β†’ Need. Used for leadership briefings.