Mojo Bridge
Mojo Dialer → BigQuery sync. Captures setter dialing activity + the full owner/property/phone graph Mojo holds, so the setter-program deliverables can join Mojo data to FUB on normalized phone. Lives at
~/rylobasic/systems/setter-program/mojo-bridge/.
Why
Mojo has no public API but its SPA at app201.mojosells.com/v2/rest/* is stable and session-cookie authed. The 12,425 contacts on account 689102 carry MLS metadata, AVM history, multi-phone/multi-email arrays with per-channel DNC flags, and full MLS remarks — none of which surface in the Mojo↔FUB native integration. Without this bridge, every call our setters will make lives only inside Mojo's UI, with no programmatic way to track dial volume, talk time, or outcome attribution against FUB consults.
The bridge buys time. The long-term answer is Custom Dialer (Twilio + STT + Vertex, ~Sept 2026). Until then, BQ becomes the system of record while Mojo keeps doing the four things it does well: power dialing, skip tracing, FSBO/Expired feeds, MLS metadata enrichment.
Current state
- Status: operational (full deploy + self-monitoring shipped 2026-05-26)
- Last update: 2026-05-26
- Blocked on: nothing — waiting on setters being added to Mojo account 689102
What's live as of 2026-05-26:
- Scaffold at ~/rylobasic/systems/setter-program/mojo-bridge/ (MR !198 merged)
- Cloud Run service pos-mojo-bridge at https://pos-mojo-bridge-uvqirrk5la-uc.a.run.app — image :ab28bc12, revision 00001-sv6 (MR !200)
- Cloud Scheduler pos-mojo-bridge-mojo-daily-ingest daily 06:00 CT → POST /mojo-daily-ingest
- Tfvars + Terraform at infra/terraform/pos-mojo-bridge/ (MR !201)
- Drift coverage — check-tfvars-drift.sh now governs 8/8 services including mojo-bridge (MR !202)
- Mac-mini launchd refresh runner com.rycolston.mojo-session-refresh — every 15 min polls sync_log for session_expired, drives Playwright through Mojo login, writes new MOJO_SESSION_COOKIE version. Reads creds from 1P item mojosells.com in POSPJ vault (MR !203)
- Cloud Monitoring alert policy mojo-bridge: non-ok rows in sync_log — scheduled query daily 06:45 CT writes to mojo_raw.sync_log_alerts, RAISEs on non-empty, alert fires email to [email protected] (MR !204, synthetic-row tested end-to-end)
- B4 auto-apply detects hyphenated service names (MR !205 — bug surfaced by mojo-bridge being the first multi-word pos-* service)
- GCS bucket gs://pospj-480915-mojo-bridge/ with 2-year lifecycle on raw/
- 12 mojo_raw.* BQ tables + mojo_raw.sync_log_alerts (alert dest)
- Secret Manager MOJO_SESSION_COOKIE v3 — auto-refreshed by Mac-mini runner when stale
- Deployed daily-ingest smoke-test 2026-05-26 08:34 CT: 5/5 endpoints OK, 1 time_sheet + 76 posting_history rows landed via the actual Cloud Run service (run_id 34484651-d268-4506-88a9-c783c8cfa0a4)
What's NOT live (parked, not blocking):
- Recording audio mirror to GCS (90-day cliff). Stub endpoint /mojo-mirror-recordings returns {"status":"not_implemented"}.
- Cold-load helper for first backfill of all 12,425 contacts.
Next 3 actions
- Wait for first setter to land on Mojo account 689102 (Appointment Setter Hiring)
- Implement recording mirror (90-day cliff is the only forcing function). Wire GCS lifecycle + signed URL fetch.
- When the cookie naturally expires (~7-30 days), confirm the launchd runner self-heals — that's the load-bearing reliability claim and only a real expiry can prove it.
Decisions log
- 2026-05-25 — Bridge over replacement. Don't replace Mojo with OpenPhone or similar. OpenPhone isn't a power dialer and replacing Mojo means replacing 4 products (dialer + skip-trace + FSBO/Expired feed + MLS enrichment), not 1. The bridge makes BQ the system of record while Mojo keeps doing its job. Custom Dialer (Twilio) remains the long-term replacement on its existing roadmap.
- 2026-05-25 — Setters on Ry's Mojo account, not separate. Account 689102 has Owner-level access; setters get added as agents under it so all dialing flows through one bridge.
- 2026-05-25 — Cookie refresh runs on Mac mini, not Cloud Run. Mojo's WAF likely challenges unfamiliar IPs on login; residential IP from Mac mini is unchallenged. Cloud Run egress untested but assumed risky.
- 2026-05-25 — Raw archive to GCS before BQ write. Every endpoint's raw JSON is archived before any transform runs. If a load fails, we replay from GCS without re-hitting Mojo. Reliability over throughput throughout.
- 2026-05-26 — Reuse
pos-cloud-functionsSA, not dedicated. The runtime SA already has secretmanager.secretAccessor + bigquery.dataEditor + storage.admin at project scope. A dedicatedpos-mojo-bridge-sais cleaner but YAGNI right now; can split later without service downtime. - 2026-05-26 — Bootstrap tfvars in separate MR.
reject-hand-edit-tfvarsblocks non-auto-bump branches from touchingpos-*/terraform.tfvars. Scaffold MR ships without tfvars; anauto-bump/manual-pos-<svc>-bootstrapMR pins the first SHA after the build job lands the image. Documented ininfra/cloud-run-services/deploys.md"Bootstrapping a new pos-* service". - 2026-05-26 — Script-style scheduled query (no destination_table_name_template). The alert SQL is a multi-statement script that handles
CREATE OR REPLACEitself and RAISEs on non-empty. Without removingdestination_dataset_idfrom the DTS config, BQ DTS pre-validates the (empty) script's "default dataset" against the config's destination and rejects with "Dataset specified in the query ('') is not consistent with Destination dataset 'mojo_raw'". Script-style configs omit destination_dataset_id entirely. - 2026-05-26 — Alert via "RAISE → DTS error log → log-based metric → policy". BQ DTS has no native "email when result is non-empty". Workaround: have the SQL itself RAISE when the result is non-empty, making the transfer run report ERROR. Cloud Logging captures that ERROR, a log-based metric counts those entries, and a Monitoring alert policy emails on metric > 0. End-to-end tested with a synthetic
session_expiredrow.
Related projects
- Custom Dialer — the long-term replacement
- Appointment Setter Hiring — setters who'll dial through this account
- Marissa Tah Setter Program — the program that consumes this data
- Setter Workbook System — handbook.rycolston.com setter UI
- BQ Bulletproof — engineering standards this bridge follows
Open issues
- GitLab: label
mojo-bridge(to be created when first issue lands). - MR record: !198 (scaffold, 2026-05-25), !200 (Cloud Run + Terraform + CI, 2026-05-26), !201 (bootstrap tfvars), !202 (drift coverage), !203 (refresh-runner schema + 1P item fix), !204 (Cloud Monitoring alert policy), !205 (auto-apply regex hyphens fix).
- Known oddity:
GET /healthzreturns Google GFE-styled 404 despite being declared inentrypoint.pyand listed in/openapi.json. ProductionPOST /mojo-daily-ingestandPOST /mojo-mirror-recordingswork. Non-blocking. Likely GFE-layer path interception — investigate when there's time.
Reference
- Endpoint catalog:
~/rylobasic/systems/setter-program/mojo-bridge/docs/endpoints.md - Reliability design + open items:
~/rylobasic/systems/setter-program/mojo-bridge/docs/reliability.md - Session refresh design:
~/rylobasic/systems/setter-program/mojo-bridge/docs/refresh.md - Session refresh runner (Mac-mini install + ops):
~/rylobasic/systems/setter-program/mojo-bridge/scripts/session-refresh/README.md - Property attr type code map:
~/rylobasic/systems/setter-program/mojo-bridge/docs/property_attr_types.md - Cloud Run service home:
~/rylobasic/infra/cloud-run-services/pos-mojo-bridge/ - Terraform:
~/rylobasic/infra/terraform/pos-mojo-bridge/(pos_mojo_bridge.tfservice + scheduler;alert.tfDTS + monitoring) - Bootstrap flow for new pos- services:
~/rylobasic/infra/cloud-run-services/deploys.md("Bootstrapping a new pos- service") - Alert SQL:
~/rylobasic/systems/setter-program/mojo-bridge/sql/alert_sync_log.sql