BioCreative Strategies × CARR Biosystems Azure Migration Program

Application & Edge Functions

The frontend, identity, and compute story, plus a full census of all 26 edge functions and where each one lands on Azure.

Edge Function Census (all 26)

Download .docx

08 - Edge Function Census (all 26) + Azure Landing

The "what code runs off-database, and where does it go on Azure" doc. A full census of every CARR edge function, built from a read of each supabase/functions/*/index.ts on 2026-07-21. Companion to 07_azure_sql_conversion.md (which covers the in-database code). The edge functions are the application logic that lives outside Postgres today and must be re-hosted on Azure compute.
Triggers: "edge functions", "how many functions", "deno functions", "what moves to azure functions", "ef census", "container apps".

Why this matters

CARR runs on Supabase Edge Functions (Deno / TypeScript). Azure has no Supabase-edge runtime, so all of this code must be re-hosted. The census below drives the compute side of the migration the same way the object census in 07 drives the data side. The prior pack said "19 Edge Functions" (05_carr_migration_map.md row 6); the live count carried into the migration is 26.

Excluded from the migration: the sync-social-listening function (LinkedIn post + engagement monitoring / ELG) is not part of this migration. Social listening was never fully built out on the BC side, so it is not being handed over or re-hosted. It is out of scope here and, if ever wanted, would be a separate future effort - not a migration deliverable.

How they land on Azure (the pattern)

The recommended internalization is one Azure Function per edge function (Node/TypeScript on the Functions runtime, or a Deno container on Azure Container Apps where we want to preserve the Deno code verbatim), fronted by:

  • Azure Key Vault for every secret currently read from Supabase Vault (get_vault_secret / vault_read).
  • Timer triggers (or Logic Apps schedules) for the pg_cron-driven syncs.
  • HTTP triggers for the webhooks (Clay, EmailBison, HeyReach) and the UI-invoked functions.
  • Microsoft Foundry Model Router for every AI call (replacing the direct Anthropic / OpenAI HTTP calls), giving BW one governed model endpoint with Entra identity + observability.

The AI functions are the cleanest win: the model call is a single fetch to a provider today, so repointing it at Foundry Model Router is a small, contained change per function.

Technical per-function census (for engineers)

Effort: S = ~1 day. M = ~2-4 days. L = ~1 week+. All secrets today resolve from Supabase Vault via get_vault_secret / vault_read, with an env-var fallback.

# Function What it does Trigger External API Secrets Azure target Effort
1 batch-generate-outreach Bulk-generates outreach messages by buying persona + ICP tier from campaign_goals UI / manual Anthropic (primary), OpenAI (fallback) ANTHROPIC_API_KEY, OPENAI_API_KEY Azure Function + Foundry Model Router M
2 generate-outreach-message On-demand single message (5 data layers: persona, enrichment, positioning, sender, category) UI Anthropic + OpenAI fallback ANTHROPIC_API_KEY, OPENAI_API_KEY Azure Function + Foundry Model Router M
3 generate-content LinkedIn post / content generation with outreach_positioning context UI Anthropic (Sonnet) ANTHROPIC_API_KEY Azure Function + Foundry M
4 generate-carr-daily-brief AI daily intelligence brief from news + trials, banded include/exclude + editorial intro cron / manual Anthropic ANTHROPIC_API_KEY Azure Function (timer) + Foundry M
5 generate-carr-newsletter Preview-only branded newsletter from client_news_intelligence manual Anthropic (Sonnet 4.5) ANTHROPIC_API_KEY Azure Function + Foundry S-M
6 validate-data-flag AI validation of a flagged data point via web search UI Brave Search, Anthropic BRAVE_API_KEY, ANTHROPIC_API_KEY Azure Function + Foundry + Bing/Grounding M
7 validate-rep-note AI validation of a rep note via web scrape UI Firecrawl, Anthropic FIRECRAWL_API_KEY, ANTHROPIC_API_KEY Azure Function + Foundry M
8 transcribe-audio Whisper transcription of rep voice notes UI OpenAI Whisper OPENAI_API_KEY Azure Function + Azure OpenAI Whisper / Speech S
9 sync-emailbison Pulls Bison campaigns, contacts, accounts, stats into bison_* pg_cron (6h) EmailBison emailbison_api_key Azure Function (timer) M
10 push-to-emailbison Pushes ready_to_send email messages into Bison campaigns; emits Heart heartbeat pg_cron (2h) EmailBison emailbison_api_key Azure Function (timer) M
11 emailbison-webhook Inbound Bison events: opens, replies, bounces, OOO, unsubscribe; noise filtering webhook (inbound) service role Azure Function (HTTP) M
12 emailbison-reply Sends a reply via Bison; stamps handoff-to-inbox; workspace-confinement guard UI EmailBison emailbison_api_key Azure Function S
13 emailbison-forward Forwards a Bison lead/reply to a rep UI EmailBison emailbison_api_key Azure Function S
14 carr-bison-reconcile Reconciles leads/stats for Bison campaigns 17-20 manual / cron EmailBison emailbison_api_key Azure Function (timer) M
15 bison-camp-bump-limits PATCHes per-campaign daily send + new-lead limits manual / admin EmailBison emailbison_api_key Azure Function S
16 bison-clear-campaign-leads Clears leads from a Bison campaign manual EmailBison emailbison_api_key Azure Function S
17 bison-lead-inspect Inspects a single Bison lead (debug/support) manual EmailBison emailbison_api_key Azure Function S
18 send-linkedin-messages Delivers ready_to_send LinkedIn messages via HeyReach pg_cron (2h) HeyReach heyreach_api_key, anon Azure Function (timer) M
19 sync-heyreach-accepted Safety-net poll for ConnectionAccepted; mirrors to outreach_messages pg_cron (15m) HeyReach heyreach_api_key Azure Function (timer) S
20 sync-heyreach-replies Backfill + sweep inbound LinkedIn reply text pg_cron HeyReach heyreach_api_key Azure Function (timer) M
21 sync-heyreach-overall-stats Pulls HeyReach overall stats (60-day window) cron HeyReach heyreach_api_key Azure Function (timer) S
22 heyreach-webhook Inbound HeyReach events (11 types) -> replies + accepted links webhook (inbound) service role Azure Function (HTTP) M
23 heyreach-provision-campaign Creates + provisions a HeyReach campaign with sequence templates UI / manual HeyReach heyreach_api_key Azure Function M
24 heyreach-campaign-control Pause / resume / stop HeyReach campaigns UI HeyReach heyreach_api_key Azure Function S
25 create-heyreach-lead-lists Creates HeyReach lists from campaign contacts (batches of 100) manual HeyReach heyreach_api_key Azure Function S
26 clay-enrichment-callback Receives Clay post-back; fills contacts.clay_* by contact_id (don't-clobber) webhook (Clay) (inbound) service role Azure Function (HTTP) S

Leadership crosswalk (grouped by job)

Group Functions What it accomplishes Azure landing Effort
AI generation batch-generate-outreach, generate-outreach-message, generate-content, generate-carr-daily-brief, generate-carr-newsletter, validate-data-flag, validate-rep-note, transcribe-audio Every message, brief, newsletter, content post, data check, and transcription Azure Functions + Foundry Model Router (one governed model endpoint) M (8 functions)
Email send + sync (EmailBison) sync-emailbison, push-to-emailbison, emailbison-webhook, emailbison-reply, emailbison-forward, carr-bison-reconcile, bison-camp-bump-limits, bison-clear-campaign-leads, bison-lead-inspect The full email outreach loop: sync in, push out, capture events, reply/forward, admin Azure Functions (timer + HTTP), Key Vault for the Bison key M (9 functions)
LinkedIn send + sync (HeyReach) send-linkedin-messages, sync-heyreach-accepted, sync-heyreach-replies, sync-heyreach-overall-stats, heyreach-webhook, heyreach-provision-campaign, heyreach-campaign-control, create-heyreach-lead-lists The full LinkedIn outreach loop: provision, send, capture accepted + replies, control, stats Azure Functions (timer + HTTP), Key Vault for the HeyReach key M (8 functions)
Enrichment intake clay-enrichment-callback Landing point for Clay enrichment post-backs Azure Function (HTTP); see 09 for the upstream half S (1 function)

Migration notes

  • Secrets: consolidate all keys (emailbison_api_key, heyreach_api_key, ANTHROPIC_API_KEY, OPENAI_API_KEY, BRAVE_API_KEY, FIRECRAWL_API_KEY) into Azure Key Vault, read via managed identity. This replaces get_vault_secret / vault_read.
  • Scheduling: the timer functions replace pg_cron jobs (see 07 for the 21-job census). If BW keeps a database scheduler, SQL Agent (Managed Instance) or Elastic Jobs (SQL Database) can call the HTTP functions instead.
  • Webhooks: the three inbound webhooks (Clay, EmailBison, HeyReach) need public HTTPS endpoints with the same URLs re-registered in each vendor's dashboard. Front with Azure API Management if BW wants a single ingress + auth layer.
  • Workspace confinement: emailbison-reply (and the Bison sync) enforce single-workspace confinement (CARR = EmailBison workspace 4). Preserve this guard on Azure so a mis-scoped key cannot send from another client's workspace.
  • AI model calls: all eight AI functions call Anthropic or OpenAI directly. Repointing each fetch at the Foundry Model Router endpoint is the recommended change (Entra identity, cost routing, observability) and is a small per-function edit.

Cross-references

  • In-database code (functions, triggers, RLS, cron): 07_azure_sql_conversion.md
  • Upstream data-acquisition tooling (Sales Nav, PhantomBuster, Clay, FullEnrich, Hub): 09_upstream_data_agents.md
  • Component crosswalk: 05_carr_migration_map.md
  • Foundry / model router: 02_ai_foundry_agents.md
  • Compute host options: 03_compute_frontend.md

Compute & Frontend Hosting

Download .docx

03 — Compute + Frontend Hosting

Deep-research synthesis on Azure compute + frontend hosting for CARR. Costs are 2026 projections; GA/preview tags are point-in-time.
Triggers: "azure compute", "static web apps vite", "container apps deno", "virtual environments azure", "landing zone", "lovable replacement".

  • Frontend (Vite/React SPA) → Azure Static Web Apps (SWA) [GA] — global CDN, free managed TLS, native GitHub Actions deploy.
  • 19 Deno/TS Edge Functions → Azure Container Apps (ACA) [GA] — containerize the Deno runtime (official denoland/deno image), preserve the TypeScript code, scale-to-zero. SWA "Bring Your Own Backend" routes /api → ACA.
  • DB → Azure DB for PostgreSQL (see 01); Realtime → Web PubSub.

Compute options (for the team)

Service Status Fit for CARR
Virtual Machines GA Poor — too much ops for 19 light scripts
AKS (Kubernetes) GA Poor — overkill
Container Apps (ACA) GA Excellent — serverless containers, runs Deno as-is
App Service GA Moderate — fine for monoliths; SWA+ACA better here
Static Web Apps GA Excellent — the frontend host
Azure Functions (Flex Consumption) GA Good if you rewrite Deno→Node

Edge functions: containerize, don't rewrite

  • Path B (recommended): Dockerize Deno → ACA. Preserves TS/Deno code, migration in weeks. Secrets via Key Vault + Managed Identity.
  • Path A: rewrite Deno→Node for Azure Functions (Flex Consumption, GA). Higher effort (Deno.envprocess.env, URL imports→npm, top-level await). Only if BW mandates pure FaaS.
  • Reject porting complex TS into Logic Apps / Power Automate — visual low-code can't carry this logic (use them only for trivial scheduled triggers).

"Virtual environments" = Azure Landing Zones

BW's "virtual environments" maps to the Cloud Adoption Framework / Azure Landing Zones: a hierarchy of Management Groups → Subscriptions → Resource Groups. Dev/Test/Prod each get their own subscription for blast-radius isolation. This is foundational education for a team coming from a single Supabase project. (More governance detail in 04.)

The Lovable question — keep it, change where it deploys

  • Power Apps + Dataverse: total rewrite into low-code; loses the React SPA + the non-dev chat-edit loop. Reject.
  • GitHub Spark: preview, built for short-lived "micro-apps" (KV/SQLite) — unsuited to a 104K-contact production app. Reject for this workload.
  • Recommended: keep Lovable as the UI builder, use its native GitHub sync to push React commits into BW Enterprise GitHub → GitHub Actions deploy to SWA. Non-devs keep their chat-edit velocity; code lives in corporate Git on Azure. What's lost only if Lovable is removed: the team's rapid-iteration UX entry point.

Vite-on-SWA gotcha (real, fixable)

SWA templates default to CRA's build/; Vite outputs dist/, and the SPA fallback can swallow /assets/*.js → blank screen + MIME error. Fix: set output_location: "dist" in the Actions YAML and add staticwebapp.config.json with navigationFallback.exclude for /assets/*, *.js, *.css. (Exact config in report §4.2.)

Networking + cost

Front Door (WAF) optionally in front of SWA; backend ACA/Functions on VNet + Private Endpoints so DB traffic stays on Azure's backbone. Projected run rate ≈ $230–350/mo (SWA ~$9 + Postgres GP+HA ~$150–250 + ACA ~$20–40 + Web PubSub ~$45 + Blob ~$5 + Key Vault ~$1). Costs balloon only if BW forces dedicated App Service Environments / heavy Logic Apps.

GitHub, DevOps & Governance

Download .docx

04 — GitHub Enterprise, DevOps, Identity & Governance

Deep-research synthesis on GitHub/DevOps, identity, and governance for CARR's Azure move. Prices are 2026 list-price projections.
Triggers: "github actions azure", "oidc federated", "entra external id", "key vault managed identity", "landing zone governance", "migration sequencing".

CI/CD: GitHub Actions + OIDC (no stored secrets)

  • Repo is already moving into BW Enterprise GitHub → use GitHub Actions (over Azure DevOps) to stay in one ecosystem.
  • OIDC federated credentials are the 2026 standard: Actions requests a short-lived token from Entra ID at runtime via azure/loginzero secrets stored in GitHub. Set up a User-Assigned Managed Identity / App Registration with a federated credential scoped to the BW repo + branch/environment.
  • Use GitHub Environments (Staging/Production) with manual-approval gates. The ~88 Supabase-CLI migrations move to a standard migration tool (Flyway/Liquibase/db-migrate) run by the pipeline; Docker images → Azure Container Registry → ACA revision update.

Identity — the single biggest risk

Two user populations: internal BW/CARR staff (already in BW Entra workforce tenant) and external sales reps (no BW credentials). Two candidate models — this is an open decision:
- Entra External ID — B2B Collaboration (guest accounts) (report 04 lean): reps become #EXT# guests in BW's directory; one unified directory; BW applies Conditional Access/MFA uniformly; MAU billing with 50,000 free MAU/mo (CARR almost certainly $0).
- Entra External ID — CIAM / B2C (report 01 lean): separate consumer-facing directory; better if the external population is large/consumer-like.
- RLS impact (either way): replace auth.uid() with middleware that decodes the Entra JWT and runs SET LOCAL app.user_id = '<oid>'; rewrite policies to current_setting('app.user_id', true) (see 01). Frontend swaps supabase-js auth for MSAL.

Resolve B2B-vs-CIAM with BW IT early — it gates the RLS rewrite and the frontend auth swap.

Secrets: Key Vault + Managed Identity (zero-secret)

Replaces get_vault_secret. ACA/Functions get a system-assigned Managed Identity granted the "Key Vault Secrets User" role; code uses DefaultAzureCredential (or @Microsoft.KeyVault(SecretUri=...) env binding). Central rotation — update once in Key Vault, no redeploy.

Enterprise governance (new mental model for the team)

Coming from one Supabase project, the team meets: Management Groups → Subscriptions → Resource Groups, Azure Policy (e.g. "no public network access" — non-compliant deploys fail), RBAC least-privilege (devs get Contributor on specific RGs; BW IT keeps Owner), and mandatory tagging (CostCenter, Environment, Application) for chargeback via Azure Cost Management.

Cost / licensing (2026, list prices — projections)

  • GitHub Enterprise Cloud $21/user/mo; Advanced Security $49/committer/mo; Copilot Business $19 / Enterprise $39/user/mo. ~10 devs full stack ≈ $1,090/mo tooling.
  • Entra External ID: first 50,000 MAU free → external-rep auth likely $0.
  • Azure infra consumption ≈ $300–800/mo depending on DB tier/load (see 03 for the ~$230–350 lean estimate).

Security/compliance boundary

DB gets no public IP — VNet + Private Endpoints; devs reach it via VPN/Bastion. Logs (Postgres, ACA, Key Vault) → Log Analytics; Defender for Cloud scans posture. Expect a pre-go-live enterprise security review.

  1. Wks 1–2 — Foundation/Identity: Landing Zone + subscriptions/RGs; Entra External ID; repo → BW Enterprise GitHub; Actions + OIDC.
  2. Wks 3–4 — Data/Secrets: Key Vault (migrate secrets); Azure DB for PG in VNet; schema+data migrate; RLS refactor to session vars; pg_cron jobs.
  3. Wks 5–6 — Compute/API: containerize EFs → ACA; deploy JWT-validating API middleware; webhooks.
  4. Wks 7–8 — Frontend/Go-live: frontend → MSAL auth; deploy to SWA via Actions; pen-test + cutover.

This keeps the three BW workstreams (Claude Enterprise, GitHub migration, Azure DB/hosting) from blocking each other.

Component Crosswalk

Download .docx

05 — CARR Migration Map (Supabase → Azure)

The bridge doc. Maps every component of CARR's current Supabase build to its Azure equivalent, the migration effort, and the recommended path. Pillar notes 0104 carry the deep research; this is the at-a-glance crosswalk.
Triggers: "carr azure migration", "supabase to azure", "what moves where", "migration effort".

⚠ 2026-06-17 client decision — DATA TIER SUPERSEDED. BW IT locked Azure SQL (T-SQL) as the database target, not Postgres. Rows 1–11 below describe the Azure DB for PostgreSQL path BC recommended (kept for context + to show the cost of the decision). For the actual Azure SQL conversion — difficulty census, per-object rewrites, data-transfer runbook — use 07_azure_sql_conversion.md. Compute/identity/frontend rows (6–14) are largely unaffected by the engine choice.

Component crosswalk

# CARR today (Supabase) Azure target Carries over? Effort BC recommendation
1 Postgres DB (~88 migrations, complex schema) Azure Database for PostgreSQL (Flexible Server) ✅ near lift-and-shift M Correct target. pg_dump/pg_restore or Azure DB Migration Service. NOT Azure SQL.
2 Postgres RLS (rep vs admin) Native PG RLS on Flexible Server ✅ syntax identical S Policies port unchanged — EXCEPT the auth.uid() source changes (see row 9).
3 auth.getUser() → user ID stamped into rows (actor_id, skipped_by) App passes Entra ID object-id into rows ⚠️ pattern survives, source changes M Rework once auth moves to Entra (row 9). Biggest data-model touchpoint.
4 ~17 pg_cron jobs pg_cron (via azure.extensions allowlist) or Azure Functions Timer / Logic Apps schedules ✅ pg_cron supported S–M Keep pg_cron where possible (least change); move to Functions Timer only if BW governance prefers.
5 DB triggers (incl. 300-char LinkedIn guard) PG triggers on Flexible Server S Port as-is.
6 26 Edge Functions (Deno/TypeScript) Azure Functions (Node/TS) or Container Apps (Deno container) or Logic Apps/Power Automate ⚠️ rewrite/repackage L Biggest code effort. Full per-function census + Azure landing in 08_edge_function_census.md. Containerizing Deno onto Container Apps preserves the most code; Logic Apps only for simple webhook glue.
7 Supabase Vault (get_vault_secret) Azure Key Vault (read via managed identity) ⚠️ rewire S Straightforward; rewire secret reads in EFs + CI.
8 Realtime (light — notifications) Azure Web PubSub / SignalR (NOT Event Grid) ⚠️ rewrite if used S BW proposed Event Grid — wrong for client WebSockets. Low usage; re-implement only the live-notification path.
9 Supabase Auth (reps + admins) Entra ID + Entra External ID (CIAM) for non-BW reps ❌ replaced L (highest risk) The hardest piece. External sales reps are NOT in BW's directory → need External ID / B2B / custom flow. See 04.
10 Storage (light — docs, images) Azure Blob Storage (or SharePoint) ⚠️ re-point S Blob Storage; SharePoint only if BW insists.
11 Custom API access (PostgREST auto-API) Custom Node/TS API middleware on ACA (validates Entra JWT, injects SET LOCAL app.user_id) ❌ must build L Required — replaces PostgREST AND carries the RLS identity injection. Frontend swaps supabase-js for fetch/MSAL.
12 Lovable (chat-driven React/Vite UX builder) Static Web Apps / App Service (host) + keep Lovable as build tool, OR Power Apps / GitHub Spark ⚠️ host moves; builder contested L Keep Lovable as builder, host output on Azure. Power Apps/Dataverse path loses the non-dev UX loop — push back (see 03).
13 Frontend hosting (Lovable-hosted Vite SPA) Azure Static Web Apps (note Vite build config) or Container Apps ⚠️ M Static Web Apps; mind the Vite-not-CRA build-output gotcha.
14 Source repo carr-biosystems-hub (BC GitHub) BW Enterprise GitHub 🟡 in motion Workstream B underway (Madhavan + Binoy).
15 (no RAG today) pgvector on Flexible Server (future) ✅ available Free option later — pgvector is GA on Flexible Server.

Effort: S = days · M = ~1 week · L = multi-week.

The strategic path (corrected by deep research — then overruled by client on the engine)

Engine note (2026-06-17): the recommendation below to use Azure DB for PostgreSQL was overruled by BW in favor of Azure SQL. The compute/EF/Lovable/identity reasoning still holds; only the database engine changed (and with it, the data tier became a T-SQL rewrite — see 07). The text below is preserved as the original analysis.

Self-hosted Supabase on Azure is NOT viable on managed Azure PG: the Supabase stack needs the BYPASSRLS privilege (restricted) and the pgjwt extension (unsupported) on Flexible Server. The only workarounds — raw Postgres on an IaaS VM (defeats BW's managed-cloud mandate) or a Supabase fork (maintenance nightmare) — are both rejected. (This supersedes an earlier draft that floated self-hosted Supabase as the lean.)

Recommended: Azure-native services, sequenced to minimize rewrite:
1. Data: Azure DB for PostgreSQL Flexible Server (lift schema + RLS; refactor auth.uid() → session vars). Fast win, satisfies data-residency.
2. Compute: containerize the 26 Deno EFs onto Azure Container Apps (preserves the TS code — weeks, not months) rather than rewriting to Node/Functions.
3. Frontend/UX: keep Lovable as the builder via GitHub sync; host the React/Vite output on Static Web Apps.
4. The genuine rewrite: a custom API middleware tier (Entra JWT validation + RLS injection) and the auth swap (supabase-js → MSAL/Entra). This is the unavoidable cost of leaving Supabase's bundled middleware.

Net: Azure-native where Supabase's middleware can't follow, but Deno code and the Lovable loop are preserved.

Highest-risk items (call these out to BW)

  • Identity (row 9) — external reps + Entra is the single biggest unknown. Resolve the External ID / B2B / custom-flow decision early; it gates the RLS rework (row 3).
  • Edge Functions (row 6) — 26 Deno functions; the rewrite-vs-containerize choice drives the timeline. Per-function census in 08_edge_function_census.md.
  • Lovable (rows 12–13) — losing it removes the team's UX entry-point. Frame this as a capability loss, not a dev convenience.

Open questions

  • Which compute host does BW standardize on (Container Apps vs App Service vs AKS)?
  • Entra-only, External ID, or hybrid for the rep population?
  • Does BW accept keeping Lovable as the builder, or mandate Power Platform?
  • pg_cron retained, or all scheduling moved to Azure-native?