Architecture & engineering

Upbeat Data Bridge: The Full Entity Map (Both Strokes)

Extends the drivers arc to its logical end-state: everything in Upbeat finds a home in the Agend suite, flowing down (Upbeat → Agend) via source drivers and up (Agend → Upbeat) via the write-back outbox — so the Agend product suite acts as the data bridge, and spokes (Agend Pro WordPress, portal, mobile, client-built tools) never touch Upbeat at all. Sources: .docs/reports/upbeat-domain-map.md (endpoint-level inventory, §1.1–1.10 + driver phasing §4), writeback-two-way-sync.md (up-stroke machinery, tiers W1–W4), ADR-001 (custom objects for the long tail). Status: PROPOSED MAP · 2026-07-07

1 · The bridge model

              DOWN STROKE                          UP STROKE
   Upbeat ──[source drivers: Singer taps,──▶  Agend suite  ──[transactional outbox,
             watermark + content-hash]         (Supabase hub)  reverse-map, echo guards]──▶ Upbeat

   SPOKES (never touch Upbeat): Agend Pro WP · portal · mobile · client tools · widgets
                     ▲ read via /v1 API ▼ write via /v1 API (hub-first, async propagate)

Every entity gets a direction class, which decides what machinery it needs:

ClassDownUpMachinery
Mirror (ref data)driver only; Upbeat-owned, read-only in Agend
Ledger (append-mostly)append opsdriver + outbox creates (no update conflicts)
Shared entitydriver + outbox + field-ownership + conflict queue
Agend-nativeno Upbeat leg (Loop threads, LMS content, custom objects)
Migrate-candidateone-off importoptional legacy feedadopt the native module; Upbeat leg retired
Oracle (request-time compute)synchronous hub→Upbeat call at request time; neither stroke — a question, not a record

The Oracle class, spelled out (asked directly 2026-07-08): the outbox governs mastered writes; it does not forbid synchronous calls where Upbeat is the computation engine. Canonical example: cart pricing — the legacy build creates a draft invoice per cart (Session::create_new_invoiceadd_product_to_invoice) so Upbeat returns the real member-specific price, and checkout totals are the CRM's. That flow survives intact; what changes is who makes the call: the hub's connector (same OAuth/token machinery as the drivers), never the client's WordPress. Natural API home: the gateway cart's checkout/simulate route. Required hygiene (the async-proposal lesson): timeout + "price confirmed at checkout" fallback, quote caching in the hub, and a shrink path — price groups/price points already sync down, so each pricing rule mirrored into the hub removes a synchronous dependency. Strangler pattern, applied to computation.

2 · The map — every Upbeat domain, its Agend home, both strokes

WP column = what the legacy WordPress build imports this into today (the thing the bridge replaces).

#Upbeat domain (entities)Agend homeDown strokeUp strokeReplaces in WP
1Members (MemberDetail, referrals, login)CRM contacts✅ shipped (upbeat-contacts)W1 update · W3 create/passwordWP Users + kiosk member sync
2Membership catalogue (MembershipType/Grade/Plan, upgrade grades, regrade products)CRM tiers + membershipssmall ref driver (map §4 Phase 3)W3 (upgrade/renewal requests)WooCommerce membership products
3Organisations (Account, subsidiaries, emailDomains, OrganisationRole, org categories, AdminAccounts)CRM companies + contact relationships (org roles map naturally onto crm_contact_relationships)next major driver (map §4 Phase 4)W3 account update, role CRUDcompany-admin / child-accounts plugin logic
4Invoices & payments (Invoice, payment lines, PaymentPlan, PaymentProfile, CashSaleAccount)CRM sales / billing✅ shipped (upbeat-sales)W2 (invoice groups, record payment)WooCommerce orders + pay-invoice flow
5Products & pricing (Product, PricePoint, PriceGroup, Coupon, PostageCost, QuickUrl)Commerce/cart (+ events products)driver TBD (products feed)W2-adjacent (lines ride invoices)WooCommerce products, coupons, shipping
6Events cluster (Event, child events, EventPackage, EventRegistration, Registration, waitlist, Speaker/Sponsor profiles, EventRequest, EventSurvey, Location)Events app (native)DECISION: sync vs migrate — §4W4 or retired by migrationThe Events Calendar (Tribe) + attendee forms + event products
7Entitlements (Entitlement, EntitlementType)Benefits/entitlements engine (crm_benefits + @agend/entitlements)high-value driver — domain map ranks this Phase 2: the CRM→LMS access bridgeentitlement/update (W3)financial-member-only gating
8Comms preferences (Member_Subscriptions, dynamic subs* booleans)CRM consent/preferences (feeds existing Mailchimp sync)small driversubscriptions update (pairs with W1)GF preference forms
9CPD & education (CPD_Points, CPD_Type, Course, CourseEnrolment, Assessment, Qualification, EducationHistory, Institution, Subject)LMS (CPD ledger, enrolments, assessments) + CRM (quals on the contact)driver (ledger class — append-heavy)qual/education CRUD (Tier 1), CPD append (LMS-earned points flow UP: courses completed in Agend LMS must land in Upbeat's CPD ledger)CPD displays, qualification lists
10Committees & governance (Committee, CommitteeActivity, MemberCommitteeActivity)Loop (committee/user integration sync already exists in apps/loop) + CRM relationshipsextend existing loop sync into a proper driverrole changes (Tier 5)committee pages
11Service & engagement (Case, Annotation, Task, EmailReport)CRM tasks/notes + service-request automation (.docs/specs/2026-07-04-service-request-automation)on-demand (case detail)create ops (Tier 5) — cases/notes raised in Agend flow upcontact forms → CRM
12Loyalty (Loyalty, LoyaltyPoint)Custom object — no native module; first candidate for the generic driver (O4)via generic driver → object_records— (display-only v1)loyalty widget
13Ref data (Region, IndustryType, Licensee, PriceGroup, Subject, Institution)CRM/LMS ref tables or custom objects via generic driver sweep (O4)Mirror class — one config-driven pass— (Upbeat-owned)ACF choice lists (legacy acf.php loads these into field options)
14Member card / barcodePortal (derived — membership status + barcode generation, not a synced entity)derived from #1membership card/barcode widgets

Coverage check: every object class in the legacy connector's inventory (~45 read families, ~45 write methods) lands in exactly one row above. The long tail (12, 13) needs no bespoke drivers if O4 (generic driver → custom objects) is built — that single capability closes the map.

3 · How "both ways" works per entity (the storage contract)

  1. One field-ownership registry per entity (extends the pattern shipped in crm-loader.ts for contacts): each field is Upbeat-owned (down only, read-only in Agend UIs), Agend-owned (up only, ingest never overwrites), or shared (both strokes + conflict queue per writeback doc §7).
  2. Down stroke = the existing driver pattern unchanged: Azure-AD auth, paged fetch, watermark delta, content-hash skip, per-account Inngest orchestration. New entities are new drivers (or generic-driver configs), not new architecture.
  3. Up stroke = the writeback outbox unchanged: hub-first write, transactional enqueue, ordered operation groups, echo prevention via content-hash convergence. New entities are new WriteBackOperation types + reverseMap rules, not new architecture.
  4. The strangler contract for Agend Pro WP: per entity row, once BOTH strokes are live, the WP integration flips from Iugo_Membership_Kiosk_API to the Agend API — and that slice of the proprietary plugin retires. The bridge map is therefore also the plugin retirement schedule (and the code-protection sunset schedule).
  5. Ledger-class nuance (row 9): CPD is the clearest case where Agend originates data — LMS course completions must flow UP into Upbeat's CPD ledger. Append-only ops avoid the conflict problem entirely; this is the easiest "up" class after profile updates.

4 · The events decision: sync vs migrate (O3)

The hardest driver (~40% bespoke per the pilot doc) vs a native module that already exists. Decide per this framework before W4 work starts:

CriterionFavours SYNC (build the driver)Favours MIGRATE (adopt native Events)
Where does staff run events day-to-day?Dynamics/Upbeat screensAnywhere (they'd retrain to Agend)
Financial couplingEvent revenue must post through Upbeat invoicing regardlessInvoicing already bridged via rows 4–5; events revenue can ride it
Model fitUpbeat's packages/price-groups are load-bearingNative tickets/packages cover the real usage
Effort shapeHardest driver + W4 write-backOne-off migration + staff change management
End-stateTwo event systems forever, bridgedOne event system; Upbeat events become history

Recommendation to evaluate first: a hybrid — migrate event management to native Events, keep a thin financial up-stroke (registrations → Upbeat invoices via the already-planned W2 machinery). That deletes the events driver and W4's registration write-back, keeping only money flows Upbeat genuinely needs. Needs a client-workflow audit (who touches events in Dynamics today?) before committing.

5 · Sequencing (merging the domain map's phases with write-back tiers)

OrderSliceWhy now
1Entitlements down (row 7)Domain map's own "highest value" call — unlocks access gating platform-wide
2Orgs down (row 3) + membership catalogue (row 2)Completes the CRM graph; prerequisite for W3
3W1/W2 up-strokes (already designed)Contacts + invoices become truly bidirectional
4CPD ledger both strokes (row 9)Easiest up-class after W1; high member value
5Generic driver → custom objects (O4)Closes rows 12–13 and all future long tail as config
6Events decision (§4), then execute chosen pathDeliberately last — the decision may delete the hardest work

6 · Client configurations: the bridge is optional per client

Because the hub is the system of record natively, Upbeat is a configuration, not a dependency. The same suite (and the same thin WordPress plugin + widgets + API) serves three client types — only the back-of-hub differs:

ConfigurationDown strokeUp strokeWho
BridgedUpbeat drivers (this map)Write-back outbox (W1–W4)Clients with a Dynamics investment worth preserving (AHRI et al.)
MigratedOne-off import (Groundhogg transformers in apps/crm/lib/migration, wp-migration-* migrations), then noneNone — hub IS the recordLegacy Agend-AMS clients; parity check first: legacy-ams-parity-map.md
NativeNew clients, no prior CRM

Migrated and native clients skip this entire map: no drivers, no field ownership, no conflict queue, and every write capability is available immediately (W1–W4 exist only because of Upbeat). One WordPress build serves all three (opportunity O6, platform-opportunities.md).

7 · What this map is

It is the answer to "can everything in Upbeat live in Agend?" — yes: 12 domains have native homes, 2 ride custom objects, and one (events) has a decision to make that might be cheaper than anyone assumed. It is simultaneously the driver roadmap, the write-back roadmap, the WP plugin retirement schedule, and the sunset plan for code protection — one map, four programmes aligned. And per §6, it is the maximal case: only bridged clients need any of it.

What this map is not is a mandate to rebuild Upbeat's authoring surfaces — see strategic-posture-upbeat.md for the standing answer to "are we replicating Upbeat?" and the operating rule that governs every entity flip.