M MemberIntel KB

spec

Cross-Pollination & Brain Isolation

Details the three failure modes of the cross-pollination pipeline — re-identification, tenant leakage, and opt-out bypass — and the architectural mitigations for each, including k-anonymity floors, three-role isolation, and GCP project structure.

Cross-pollination

What cross-pollination actually is, in mechanical terms.

A scheduled job (weekly or monthly per the SPEC) runs across per-customer brain entries that have positive feedback signals, uses Claude to draft candidate global-brain entries that are abstracted and anonymized, and queues those candidates for the content lead to review. The content lead approves, edits, or rejects. Approved entries enter the global brain and become available to all customers (including the customer who, unwittingly, was the source).

The risk surface has three distinct failure modes, and they need different mitigations.

Failure mode 1: Identifying details survive abstraction.

The Claude-driven drafting step is supposed to abstract and anonymize, but Claude isn’t a privacy filter — it’s a writer trying to be helpful. If a per-customer brain entry says “Tried raising annual price to $497 for my urology CME platform, lost 12 of 80 members in two weeks,” Claude might dutifully abstract it to “Operators in medical CME niches who raise annual price by ~30% can expect ~15% short-term churn” — and now anyone reading that knows there’s exactly one customer in the global brain who’s a urology CME platform.

This is the hardest version of the problem because it’s implicit re-identification. No name, no URL, no PII in the traditional sense. Just enough detail that someone who knows the niche can guess. And it’s the failure mode most teams miss because the obvious anonymization (strip names, URLs, emails) feels like enough.

The mitigation is structural, not stylistic.

First, the candidate-drafting prompt has to operate on abstracted inputs, not raw per-customer brain text. Before Claude sees anything, a pre-processing step extracts only the kinds of facts that are safe to generalize: action taken, magnitude, time-window, outcome direction, broad category tags. The customer’s specific niche, copy, and identifying language never get into the drafting prompt.

Second, every candidate carries a “k-anonymity floor” check before it’s eligible to be drafted at all. The pattern has to be observable across at least N customers (start at N=5, tune up) before it can be cross-pollinated. If only one customer ever did this thing, there’s nothing to abstract — by definition the pattern is identifying. This is the single most important rule and it’s a hard rule, not a guideline.

Third, the content lead’s review queue shows them not just the candidate text, but the source customers the pattern was drawn from and the original brain entries. The content lead’s job isn’t just “does this read well” — it’s “could a competitor or curious user reverse-engineer who this came from.” That’s a human judgment call and it has to live with a human.

Failure mode 2: The pipeline itself leaks across tenants.

This is the bug Seth’s JD specifically warns about. Cross-pollination is the one job in your system that intentionally reads from many tenants and writes to a global namespace. By construction, the RLS guardrails we built earlier in this conversation don’t apply — this code runs as the migration role or an equivalent elevated role, because it has to.

The mitigation is to treat cross-pollination as a security boundary in your codebase, not just a feature.

Concretely: cross-pollination lives in its own service or module with its own database role (call it cross_pollination_role) that has read-only access to per-customer brains, write access only to a global_brain_candidates staging table, and no access to anything else. It cannot read tenants, cannot read members, cannot read transactions. Just per-customer brain entries with positive feedback, and the staging table.

This role does not exist anywhere else in the application. The web app’s role can’t see the staging table. The content lead’s review UI uses a separate role that can read the staging table and write approvals to the global brain. Three roles, three responsibilities, no overlap.

The job runs on Cloud Run Jobs (not the same service as the API), with its own service account, its own VPC connector, its own audit logging. You should be able to grep for cross_pollination_role and find every line of code that touches it. If you can’t, the boundary isn’t real.

Failure mode 3: The opt-out doesn’t actually opt out.

The SPEC promises customers can opt out of cross-pollination contribution. That promise needs to be enforced before a customer’s brain entries are eligible for the candidate pool — not as a filter at draft time, not as a flag on the output. At the source.

The cleanest pattern: a cross_pollination_consent field on the tenant record, default true (per SPEC §6.3), with a version number. The cross-pollination job’s first SQL is SELECT tenant_id FROM tenants WHERE cross_pollination_consent = true AND consent_version >= current_version. If a customer toggles off, they’re excluded from the next run, period.

The version number matters because if you ever change what cross-pollination means (scope expands, new types of inference, etc.), every customer needs to re-consent at the new version. Their old “yes” doesn’t carry forward to new use. This is GDPR purpose-limitation hygiene and your privacy counsel will flag it if it’s missing.

Now: there’s an asymmetry here that matters. If a customer opts out after their brain content has already been used to generate global-brain entries, those approved entries don’t get retroactively pulled — they’re already abstracted, already anonymized, already serving other customers, and the source attribution at that point is a multi-customer pattern. That’s defensible, and it should be in the ToS explicitly. What opting out does is exclude the customer from future candidate generation. Make this clear in the consent flow language so there’s no surprise.

The three things this implies for your architecture.

A staging table is required. Candidates go into global_brain_candidates first. They’re not in the global brain until the content lead approves. The candidates table has a foreign key back to the source brain entries (for audit), an anonymization-status field, a content-lead-review-status field, and a created_at. Once approved, the entry is copied to global_brain_entries with the source linkage stored in a separate audit table that the global brain queries don’t see. Once rejected, the candidate is hard-deleted along with its source linkage.

Audit is non-negotiable. Every cross-pollination run logs: which tenants were eligible, how many entries were considered, how many candidates were drafted, the prompt version used, the model version, the content-lead reviewer, and approve/reject outcomes per candidate. Per the SPEC’s compliance section this needs to exist anyway. It also gives you the ability to answer “what happened to entries from customer X” if a regulator or counsel ever asks — which they will, eventually.

The content lead is part of the security boundary. This isn’t just an editorial role, it’s a privacy-review role. That has implications: the person needs training on what re-identification risk looks like, the review UI needs to surface the right information for that judgment, and rejection rate is a metric you watch. If rejection rate is below 10-15%, the upstream filtering is too loose and bad candidates are reaching review. If it’s above 50%, Claude’s drafting is bad and you’re wasting human time. Both are tunable.

Two related questions worth raising now even though they’re not strictly cross-pollination.

The first is signal weighting, which the SPEC mentions in §6.3. Free users contribute thinner signal than Pro users (5 chats/month vs unlimited). The temptation is to weight Pro-user input higher. That’s fine, but be aware that this also concentrates re-identification risk on Pro users, who are more identifiable as a population. The k-anonymity floor (N=5+ customers exhibiting the pattern) is what protects against this; signal weighting is an editorial choice that operates after the k-anonymity floor is satisfied, not instead of it.

The second is the V1.5 agent action data. Once V1.5 ships, per-customer brains will start accumulating “what the agent did and how it turned out” data — which is, frankly, the most valuable cross-pollination input the system will ever see. It’s also higher-risk because actions are more specific than chat messages. Before V1.5 build kicks off, the cross-pollination pipeline needs an explicit policy for whether agent action data is eligible at all, and if so under what additional safeguards. Easiest answer for V1.5 is “no, agent data is excluded from cross-pollination until V2.” Hardest but most valuable answer is “yes with stricter k-anonymity thresholds and a separate review track.” Worth flagging now so it’s a deliberate decision later, not a default.

The decisions to make to move forward.

  1. Confirm the three-roles model for cross-pollination (cross_pollination_role read, candidates_role staging, content_lead_role approval). My recommendation: yes.

  2. Set the k-anonymity floor. Start at N=5, willing to tune up. Lower than 3 is, in my view, indefensible.

  3. Decide cadence. SPEC says weekly or monthly. I’d start monthly during V1 launch — fewer runs, more time to catch issues, lower compute cost, and content lead isn’t drowning in queue. Move to weekly post-launch once the pattern is steady.

  4. Decide where the cross-pollination service runs in GCP. Cloud Run Jobs is the right fit (scheduled, ephemeral, isolated service account). Not the same Cloud Run service as the API.

  5. Decide on the V1.5 agent-data policy now, in writing, even though it doesn’t matter until V1.5 ships. My recommendation: agent action data is excluded from cross-pollination until V2 review.

Want to keep going on infra — next natural threads are how you structure GCP projects (single project vs prod/staging/dev separation), the CI/CD pipeline including the RLS test harness, or the secrets management layer? Or shift to a different angle, like the AI eval suite, which has its own architectural shape?

##GCP project structure

GCP project structure is the most consequential of the foundations — every other infra decision flows downstream of it. If you get this wrong it’s painful to fix later; if you get it right, environment promotion, IAM, and cost tracking all become easier.

The decision in plain terms.

A “GCP project” is the unit of isolation in Google Cloud — separate billing, separate IAM, separate quotas, separate audit logs. The decision is how many projects you create and what they contain.

The three patterns teams actually use.

Single project, environments separated by naming. One project called memberintel, with resources named api-prod, api-staging, db-prod, db-staging. Cheapest in setup time, weakest in isolation, and a real foot-gun: a misconfigured IAM grant or a fat-fingered command can hit production from a dev context. I’d cross this off for anything serving paying customers.

Project-per-environment. Three (or four) projects: memberintel-prod, memberintel-staging, memberintel-dev, optionally memberintel-shared for things like artifact registries and DNS. Each environment has its own billing line, its own IAM, its own VPC. A developer has owner-level access to dev, restricted access to staging, and read-only or zero access to prod. This is the standard pattern for serious SaaS and it’s what I’d recommend.

Project-per-tenant or project-per-tier. Some teams put each enterprise customer in their own project for the strongest possible isolation. Massive overkill for MemberIntel V1. Worth knowing it exists in case someone post-V2 asks for it as part of an enterprise deal — at that point you’d add a fourth pattern: prod is for shared-tenant Pro customers, and a small number of dedicated projects exist for enterprise. Don’t build for this now.

My recommendation: project-per-environment, with a shared services project.

Concretely, four projects:

memberintel-prod — Cloud Run services, Cloud SQL, Secret Manager, the production VPC, Cloud Tasks queues. This is the only project that holds real customer data. Access is locked down: Seth, the Senior AI Engineer, and a deploy service account. No one else has write access. Read-only access for debugging is granted via short-lived elevation, not standing grants.

memberintel-staging — mirror of prod, scaled smaller. Synthetic data only, never a copy of production. This matters: the moment you copy prod data to staging “just to debug something,” your isolation story collapses and your privacy counsel’s signoff evaporates. Hard rule.

memberintel-dev — the looser environment where engineers iterate. Each engineer can have their own Cloud Run revision, their own Cloud SQL instance if they need it. Higher IAM permissions, lower scrutiny, no real data ever.

memberintel-shared — Artifact Registry (Docker images promote across environments from here), Cloud Build, DNS zones, the GCS bucket that holds Terraform state, the KMS keyring used by all environments. This is what people sometimes call a “tooling” or “ops” project. It’s how you avoid duplicating CI/CD infrastructure three times.

Behind these four projects sits a Google Cloud Organization with folders. Recommended folder structure: production folder containing prod, non-production folder containing staging and dev, and shared at the org level. The folder structure lets you apply Organization Policies once at the folder level rather than three times — for example, “no public IPs allowed” applied to the production folder.

What this gives you, concretely.

Billing. You’ll know exactly what production costs versus staging versus dev. When the cost-per-Free-user dashboard the SPEC requires gets built, this is half the work — production billing is the cost of running the product, isolated from anything else. Without project separation, you’d be filtering by labels and hoping you got it right.

IAM. You can give Cindy read-only access to staging without it bleeding into prod. You can give the Senior AI Engineer prod write access on day one without giving them dev cleanup duties. You can give the deploy service account write access to prod only through a deploy pipeline, never directly.

Audit. Cloud Audit Logs are scoped per project. When privacy counsel asks for “all access to customer data in the last 90 days,” you query prod’s logs and you’re done. If you’d put everything in one project, you’d be filtering thousands of dev events to find the prod ones.

Quotas. GCP applies certain quotas per project. Running your dev experiments doesn’t consume prod’s quota.

Blast radius. A misconfigured Terraform run against staging can’t accidentally hit prod. A leaked dev service account key can’t see prod data.

The non-obvious part: how environments share things.

Once you have this structure, you need to decide how things flow between environments.

Docker images: built once in memberintel-shared’s Artifact Registry, then deployed to dev, staging, and prod. Same image, different config. This is the right model.

Secrets: each environment has its own Secret Manager, its own values. No shared secrets between staging and prod. Ever. The secret names might match (anthropic-api-key, stripe-webhook-secret) but the values are different. This is what lets you safely test against the Anthropic API in staging without using the prod key.

Database migrations: run automatically against staging on merge-to-main, run against prod on a manual approval step. The migration role we talked about earlier exists per environment. Production’s migration role is short-lived (issued just-in-time for the migration, revoked after), staging’s can be a standing role.

Infrastructure as code: one Terraform repo, with environments as separate state files (or workspaces). The same Terraform module produces dev, staging, and prod with different variables. Cloud SQL tier is db-f1-micro in dev, db-custom-2-7680 in staging, db-custom-8-30720 in prod. Same code, different inputs.

A practical rule that pays for itself: nothing in dev or staging ever connects to anything in prod.

No “let me just point staging at prod’s database to test something.” No “this service account in dev needs read access to prod for one query.” If staging needs to test against real-shape data, you generate synthetic data. If dev needs to debug a customer issue, you reproduce the issue in dev with synthetic data, or you read prod logs (which is fine — logs are sanitized), but you don’t establish connectivity.

The moment you punch a hole in this, every privacy claim you make later has an asterisk on it. Don’t punch the hole.

On VPCs, briefly.

Each project gets its own VPC. Cloud SQL is private IP only. Cloud Run connects to the VPC via a Serverless VPC Connector. Egress to Anthropic and Stripe goes out through Cloud NAT with a static IP — both Anthropic and Stripe can be allowlisted to your IP, and your privacy counsel will appreciate that egress is auditable rather than going through random Google IPs.

VPC Service Controls is a more advanced GCP feature that creates a perimeter around your data services and blocks even authorized identities from exfiltrating data outside the perimeter. It’s overkill for V1. Worth knowing about for V2 when the BuddyBoss partnership creates new data flows that need to be governed.

Naming and labels.

Boring but pays compounding dividends. Pick a naming convention now and apply it everywhere. Recommended:

  • Resources: <service>-<env>-<purpose>, e.g., api-prod-main, db-prod-primary, vpc-prod-default.
  • Labels on every resource: env=prod, service=api, cost-center=memberintel, tier=free|pro|shared.
  • The tier label is the key — it’s what lets you build the cost-per-Free-user dashboard the SPEC requires. Tag the Cloud SQL replica that serves Free dashboard reads as tier=free, the one serving Pro live data as tier=pro, and your billing export tells you the rest.

Set this up as Organization Policies that require certain labels at resource creation time. It’s a one-day investment that prevents months of “wait, why don’t we have labels on these instances” later.

The decisions to make to move forward.

  1. Confirm four projects under a Google Cloud Organization with production and non-production folders. My recommendation: yes.

  2. Decide who has access to what. Minimum recommendation: Seth and Senior AI Engineer have prod write; Cindy has prod read-only for compliance and dashboards; Ronald has staging write, prod read-only; everyone else dev-only until they need more. Document it. Privacy counsel will ask.

  3. Decide on Terraform from day one. With Claude Code you can scaffold this in a couple of days. Doing it later is much harder than doing it now, because retrofitting Terraform onto resources clicked into existence in the console is genuinely painful.

  4. Decide on the staging-data rule explicitly: synthetic only, never a copy of prod. Get Seth’s buy-in on this before the first time it would be convenient to violate it, because once violated it’s broken forever.

  5. Set up Organization Policies on the production folder: no public IPs on Cloud SQL, no service account key creation (use Workload Identity Federation instead), required labels.

The natural next thread from here is CI/CD and how Terraform/deploys flow between these projects, which is where the RLS test harness from the previous conversation lives, where image promotion happens, and where the manual-approval gate for prod migrations sits. Want to go there next, or pivot to secrets management (which is small but worth getting right) or the AI eval suite (which is its own architectural shape)?

For: S Seth Shoultes C Cindy Thoennessen B Blair Williams S Santiago Perez Asis