M MemberIntel KB
Activity Decisions

decision

ADR-0036: Connector shared record format for multi-platform modules

ADR-0036 (Accepted, 2026-07-17): Connector shared record format for multi-platform modules.

Status: Accepted
Date: 2026-07-17
Deciders: ai-engineer (technical sign-off), product-lead (product sign-off)

Review log

  • 2026-07-17 — ai-engineer: APPROVE-WITH-CHANGES. Core design (namespaced IDs, platform
    tag, kind axis, additive wire compat) sound and proceeds to this ADR. Required before
    acceptance: resolve the EDD platform_id ambiguity, commit to explicit schema_version in
    the envelope, flip the currency null-fallback default. All three applied to the spec.
    WishList failure-mode isolation, per-page fan-out budget, eval-fixture scoping, and the
    content_rules_summary retrieval-projection contract recorded as explicit backend
    follow-up-spec requirements (spec §12), not blocking this ADR.
    Full write-up: docs/superpowers/reviews/2026-07-17-ai-engineer-connector-shared-record-format.md.
  • 2026-07-17 — product-lead: GO-WITH-CHANGES. Wire-format contract sound. Required before
    acceptance: name the multi-platform dashboard experience as an owned follow-up (not
    “invisible plumbing”), cut the BuddyBoss course_ids membership-enrichment hitchhiking from
    this pass, keep BP-only users dropped in V1 with the conversion-signal idea parked as a
    separate future PRD needing Blair sign-off, keep cross-platform email matching strict with an
    explicit fail-closed statement. All four applied to the spec.
    Full write-up: docs/superpowers/reviews/2026-07-17-product-lead-connector-shared-record-format.md.

Context

memberintel-connect (the WordPress plugin at wordpress/plugins/memberintel-connect/) reads
MemberPress only today — its Data_Controller hardcodes MeprProduct/MeprUser/
MeprTransaction/MeprSubscription checks and raw SQL against wp_mepr_* tables. Every route
(/memberships, /members, /transactions, /subscriptions, /content, /stats) returns one
platform’s shape.

innovations#303 asks us to generalize the
plugin into a multi-platform connector with per-platform modules — MemberMouse (#301), WishList
Member (#302), Easy Digital Downloads (#299/#300), and a BuddyPress/BuddyBoss engagement module
(#289/#320) — that can co-exist on one site. #303’s own open question was the blocker: confirm
the shared record format (what a “member,” “membership,” and “transaction” look like across every
platform) before building the second module, so the first module doesn’t need reworking.

The full design rationale, field-by-field schema, and per-platform status-mapping tables live in
the spec: docs/superpowers/specs/2026-07-17-connector-shared-record-format-design.md. This ADR
records the material choices from that spec that need durable, discoverable sign-off — it does
not restate the spec in full.

Decision

Adopt the shared record format as specified, with these material choices:

  1. Namespaced string IDs, not raw integers. Every entity’s id becomes "{platform}:{platform_id}" (e.g. "memberpress:42"), so a MemberPress user #42 and an EDD customer #42 on the same site don’t collide on a single global ID. This is the load-bearing schema change — the backend site-sync pipeline must key on namespaced IDs going forward.

  2. Every record carries a required platform tag, enumerated to memberpress | memmouse | wishlist | edd | buddypress. Retrieval, playbooks, and the backend all key off this field — it’s the mechanism that keeps (for example) a MemberMouse site from getting MemberPress-specific advice.

  3. A kind axis distinguishes billing platforms (emit member/membership/transaction/subscription — MP, MemberMouse, WishList, EDD) from engagement platforms (emit no billing entities; enrich billing-platform member records via an email join — BuddyPress/BuddyBoss). A site can carry multiple billing platforms simultaneously (#300’s MP+EDD case) and up to one engagement platform in V1.

  4. Cross-platform join key: email, lowercased and trimmed, fail-closed. No fuzzy matching. A non-match means the engagement-side record contributes nothing — never a fallback into a possibly-wrong merge. When the same email legitimately belongs to member records on more than one billing platform, the engagement object is broadcast to all of them (not exclusively assigned to one).

  5. BuddyBoss is not a separate platform value. Per #289’s field-testing, BuddyBoss Platform forks BuddyPress and preserves its core REST namespace; one platform: "buddypress" module opportunistically reads BuddyBoss-Pro fields when present.

  6. Wire-format versioning: explicit schema_version: int in the response envelope, independent of the plugin’s MIC_VERSION semver. Pre-this-ADR responses have no schema_version field at all — that absence is treated as an implicit v1; this spec’s shape adds an explicit schema_version: 2. The backend accepts both the implicit-v1 (absent) and explicit-v2 shapes until the 2026-Q4 minimum-version ratchet. Plugin release version and wire schema version are different axes — coupling them would force the backend’s version-detection logic to reason about semver ranges instead of a simple integer comparison.

  7. currency is emitted at ingest whenever available, preferring per-record currency over a platform’s site-level default (MEPR_CURRENCY_CODE, edd_get_currency() are both the store’s single configured setting, not per-order data — use them only when no per-record currency exists). null → site-profile fallback is the exception path for records with neither, not the default — multi-currency EDD add-ons are exactly the case where a site-level value would be an unreliable stand-in for the actual order currency.

  8. EDD’s compound ID (edd:{product_id}:{price_id}, needed for Recurring’s per-tier billing) keeps platform_id as the bare product_id, with a new price_id field carrying the tier separately — avoiding a parsing landmine where a generic id.split(':', 1) (correct for every other platform) would silently mis-parse the embedded colon.

  9. BP-only users (community members with no paid membership) stay out of scope in V1. /members only surfaces members with at least one billing-platform record. Admitting BP-only rows is a product decision with data-subject-classification and retrieval-cardinality implications — deferred to a future PRD requiring its own product-lead + ceo-blair sign-off (and a counsel pass, per the same data-handling bar as the global-brain de-identification work), not decided as a side effect of this schema.

  10. content_rules_summary stays inlined on membership (no dedicated /content/rules endpoint), with a retrieval-layer requirement that the formatting layer summarize gated-content counts rather than serialize raw ID arrays into a prompt. The previously-proposed BuddyBoss course_ids enrichment of this same field is cut from this pass as unscoped, single-partner speculation — not part of this decision.

Consequences

Positive:

  • The four new connector modules (MemberMouse, WishList, EDD, BuddyPress) can be built against one settled contract without reworking the MemberPress module or each other.
  • Namespaced IDs + the platform tag give retrieval a server-side mechanism to avoid cross-platform advice bleed, rather than relying on prompt convention.
  • Explicit schema_version avoids coupling wire-schema evolution to plugin release cadence.
  • Existing MemberPress installs are unaffected — every change is additive; sites on the old plugin version (no schema_version field at all — v1 is implicit from its absence) see a byte-identical response to today.

Negative / costs:

  • A site running all 5 modules means up to 5 separate per-page reads (4 SQL scans + 1 external REST call) versus today’s single read — real ingest-side compute cost, not yet budgeted against shared hosting (tracked as a required backend-follow-up item, spec §12).
  • content_rules_summary and the engagement email-join both introduce new GDPR/CCPA deletion-pathway surface area that the current deletion flow doesn’t yet handle (tracked, spec §12).
  • Three new per-platform status-mapping tables (spec §9) have zero eval-fixture coverage today; a wrong mapping silently corrupts MRR/active-member figures (tracked, spec §12).

Mitigations:

  • All cost/robustness gaps above are named as explicit, non-optional requirements in the backend-side follow-up spec (spec §12) rather than left implicit — they must be addressed before the first new module (#301 or #302) ships to a real customer, per the ship-gate requirement product-lead added to that same follow-up spec.
  • The multi-platform dashboard/support experience is scoped as its own short UX spec before the module-registry refactor spec locks the API shape further, so it isn’t discovered mid-implementation.

Alternatives considered

  • A single global integer ID space — rejected; can’t guarantee uniqueness across platforms without a central ID-issuing authority the plugin doesn’t have, and would require rewriting the existing MemberPress wire contract instead of extending it.
  • Union of every platform’s native status enum, surfaced as-is — rejected; bleeds platform detail into the canonical layer and forces retrieval/playbooks to know 4 status vocabularies instead of 1.
  • Synthesizing engagement counters for platforms that don’t natively track them (e.g. WP-login-count-equivalents from usermeta for MP/WishList/EDD) — rejected; expensive per-row compute with no clean semantics and no natural stopping point for which usermeta keys count as “engagement.”
  • EDD price tiers as a variants: [] sub-field on one membership row per Product — rejected; retrieval and playbook targeting need each price tier independently addressable as its own record.
  • MIC_VERSION (plugin semver) as the schema-version signal — rejected; couples two independent axes (release cadence vs. wire-schema shape), forcing semver-range reasoning into what should be a simple integer comparison.
  • Admitting BP-only users into /members now — rejected for V1 (see Decision #9); real advisor value in the idea, but it changes MemberIntel’s data-subject scope and needs its own product/consent/counsel review, not a schema-level default.
For: S Seth Shoultes A AI Engineer B Blair Williams S Santiago Perez Asis P Product Lead