Mojo Bridge

Status
retired
Owner
Ryan Colston
Started
2026-05-25
Target launch
2026-05-26

Linear: RYR-236 — sync_log volume alerting (2026-07-23 vault→Linear audit) Verification correction (2026-07-24): sync_log volume alert is ALREADY LIVEalert_sync_log.sql + terraform data-transfer + monitoring policy ENABLED (verified 2026-07-24). (RYR-236 canceled)

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

RETIRED 2026-09-08 (RYR-540). Ry decided on 2026-08-07 to exit Mojo and closed it out on 2026-09-08 across two MRs — !3110 (the ingest scheduler, the mojo-session-refresh job + scheduler + service account, the MOJO_USERNAME/MOJO_PASSWORD secrets, the machine-dials-puller job + scheduler, the mojo-sync-log-alert BigQuery transfer and the mojo-bridge alert policy) and !3120 (the pos-mojo-bridge Cloud Run service, its mojo-mirror-recordings scheduler and the rest of the terraform).

The DATA IS KEPT — retired means the plumbing is gone, never the record. Ry, verbatim: "We're going to keep all of that as historical context, since it only costs me pennies to keep, but that's the old stuff now." Still live and still queryable (as of 2026-09-08): pospj-480915.mojo_raw.*, 143,797 rows over 17 tables and views; pospj-480915.the50os.machine_dials, 1,711 rows; and gs://pospj-480915-mojo-bridge, 65.69 MiB including 172 mirrored call-audio objects. No table, view, dataset or bucket was dropped. New recordings were judged not worth keeping, so the daily mirror stopped — what was already mirrored stays.

pos-appointment-scheduler still reads mojo_raw.contacts and contact_media and keeps working: it is a BigQuery read, never the Mojo API (verified 2026-09-08). Detail and the revert traps: memory project_mojo_bridge. Successor: Custom Dialer (RYR-57).

Historical (pre-retirement)

2026-06-30: - Recording mirror was never actually working — fixed. The audio download URL omitted the trailing slash before ?record_id; Mojo 301-redirected to the slash form and requests dropped the session cookie across the redirect → every download 401'd. 0 of 71 recordings had ever mirrored. The old code masked it by aborting at the first 401 (looked like one dead recording, 85712249). Root-caused live (slash → 200 audio/mpeg; no-slash → 301 → 401), fixed in endpoints.py (MR !958), deployed (rev pos-mojo-bridge-00010), and verified: 71/71 backfilled on first run, skiplist empty. Also hardened mirror_recordings to stay loud — a total failure always raises session_expired, never silently skiplisted (MR !958). This was the real cause of the daily mojo-sync-log-alert. Docs corrected (MR !960). - Setter Call Reviews built (manual one-off, 2026-06-30). Per-setter Drive folders of every 1-min+ recorded call, each call its own sub-folder containing a transcript Doc + the recording mp3. Lives in ry@rycolston.com Drive under "Listing Legends — Setter Call Reviews" (parent folder 1OcOKAL7a4nkbHdGkWWUB-ePH7WlQTlxP) → sub-folders Viola Hajdini — Call Reviews (13 calls), Rafferty Ojeba — Call Reviews (3), Ry Colston — Call Reviews (2). ryan.colston@orchard.com has editor access on the parent (cascades to all sub-folders). Setters NOT yet shared (await Ry's OK; would be violahajdiniii@gmail.com / raffertyojeba@gmail.com, reader). Rafferty's 3 are hand-cleaned/named; the rest were auto-built via the transcription pipeline below. NOT a recurring system yet — manual build. - Transcription pipeline (proven this session, NOT productionized). Recording mp3 (8 kHz mono, gs://pospj-480915-mojo-bridge/recordings/{record_id}.mp3) → Google Speech-to-Text v1 longrunningRecognize: encoding=MP3, sampleRateHertz=8000, model=latest_long, enableAutomaticPunctuation=true, diarizationConfig{2 speakers}. Gotchas: (1) the phone_call enhanced model returns EMPTY — use latest_long; (2) user-cred REST calls need header x-goog-user-project: pospj-480915 or 403 quota-project; (3) gcloud ml speech doesn't expose punctuation/diarization flags → hit the REST API. speech.googleapis.com enabled on pospj-480915 (2026-06-30, MANUAL — not Terraform-managed, no IaC home; no drift). Auto-punctuation is reliable; diarization is hit-or-miss on 8 kHz (clean speaker split on ~9/15 longer calls, collapsed to 1 speaker on shorter → fall back to a punctuated paragraph, no fake labels). Speaker→name mapping is a pitch-phrase heuristic. Within STT free tier (60 min/mo) at this volume. For consistent speaker labels + full productionization (mirror_recordings → STT → LLM-clean → Drive doc, daily), the upgrade is an LLM cleanup step ("Lever 2"). Scripts were ad-hoc (scratchpad), not committed.

2026-06-18: - Native "Expired Data" subscription RETIRED (decision 2026-06-18). The feed has been silent since 2026-04-26 (posting_history vendor "Mojo Expired"); rather than fix it, Ry decided the manual NTREIS→Mojo import (below) replaces it — more control over which searches/cohorts load. Let the $50/mo subscription lapse/cancel; do NOT flag the silent feed as a problem going forward. - Interim — revived the manual NTREIS→Mojo import. Pulled the Alberto (1,306) + Brandon 0-30 (80) + Brandon 1yr+ (588) Cancelled/Expired/Withdrawn saved searches into Mojo-ready CSVs (with postal code) for hand-import to calling lists. Now repeatable: data-platforms/ntreis/skills/update-ntreis/export_search_to_mojo.py + runbook data-platforms/ntreis/docs/ntreis-to-mojo-manual-import.md (MR !649). A Ryan | Cancelled/Expireds saved search also exists, not yet pulled. - mojo-bridge ingest bugfix shippedsession_summary "Total" footer row was failing the BQ load and time_sheet was crashing the MERGE on a minute-granularity key collision; fixed + deployed (MR !647/!648, rev pos-mojo-bridge-00006).

2026-06-10 audit + additions: - Audit finding — no dialing ever captured. call_detail 0 rows across 17 green runs; roster has only Ry. Anthony's dials are NOT on account 689102. - Audit finding — Mojo Expired feed silent since 2026-04-26. Verify subscription. - Audit finding — account holds 1,655 contacts across 4 main lists (Mojo Training 50, Test 0, Sample|Alberto 1,356, Vacant Land 249); the 12,425 figure from 5/25 is stale. - Contacts ingestion live: POST /mojo-cold-load?limit=N resumable backfill + endpoint=contacts slice in daily_ingest (new ids ∪ ids called in window, cap 500/run). - Recording mirror live: daily 07:00 CT scheduler mojo-mirror-recordings; every unmirrored row → gs://pospj-480915-mojo-bridge/recordings/, URI written back. - BQ views mojo_raw.v_setter_calls + v_setter_productivity created (were repo-only).

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 coveragecheck-tfvars-drift.sh now governs 8/8 services including mojo-bridge (MR !202) - Session-refresh runner — DELETED 2026-09-08 (RYR-540 Day-14 purge, MR !3110; source: gcloud run jobs list, this date). The Cloud Run Job, its scheduler, the mojo_session_refresh service account and the MOJO_USERNAME/MOJO_PASSWORD secrets are all gone. MOJO_SESSION_COOKIE survives and pos-mojo-bridge still mounts it, but nothing re-mints it — a session expiry now stops the bridge until someone logs in by hand. Handler code kept at ~/rylobasic/infra/cloud-run-jobs/mojo-session-refresh/. History: it re-minted MOJO_SESSION_COOKIE when sync_log shows session_expired, driving Playwright through Mojo login. Originally the Mac-mini launchd agent com.rycolston.mojo-session-refresh (MR !203, creds from 1P item mojosells.com in POSPJ vault). Migrated to the mojo-session-refresh Cloud Run Job 2026-06-21 (image :e9a6ec79, fired by Cloud Scheduler mojo-session-refresh-sched every 15 min, self-gates on sync_log via _refresh_needed(), creds from Secret Manager MOJO_USERNAME/MOJO_PASSWORD). The mini launchd agent (com.rycolston.mojo-session-refresh) was deleted 2026-06-24 (Phase 3 cleanup; had been disabled cold-standby). See infra/cloud-run-jobs/mojo-session-refresh/README.md. - 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 ry@rycolston.com (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_COOKIEno longer auto-refreshed*; the mojo-session-refresh Cloud Run Job that did it was deleted 2026-09-08 (was the Mac-mini runner pre-2026-06-21, Cloud Run 2026-06-21 → 2026-09-08) - 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): - Schema drift detection (status=schema_drift rows in sync_log). - Volume-based alerting (green runs with 0 rows are invisible to the current alert). BUILT — this line is wrong. sql/alert_sync_log.sql implements it: prior_dial_rows > 0 AND recent_dial_rows = 0RAISE ("dialing stalled"). Read the file 2026-07-25; RYR-236 is Canceled for this reason. - Consumers (agent-newsletter, consults-dashboard, team-coaching, handbook) — deferred until dialing data exists.

Next 3 actions

  1. Get dialing onto account 689102 DONE — resolved by live evidence 2026-07-16: call_detail 2,679 rows across 4 setters (see Current state). Replaced by: wire the first consumer (agent-newsletter dials / flywheel scoreboard setter inputs, RYR-175/RYR-172).
  2. Verify the Mojo Expired feed subscription — note the 2026-06-18 decision above already RETIRED it (let it lapse); confirm the cancellation actually happened, then drop this line.
  3. Add a volume expectation to the sync_log alertalready done, do not rebuild. alert_sync_log.sql carries it (read 2026-07-25). The live open item is instead RYR-280: confirm the $50/mo Mojo "Expired Data" subscription retired 2026-06-18 was actually cancelled.

Decisions log

Open issues

References