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 .docx08 - 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.tson 2026-07-21. Companion to07_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-listeningfunction (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
07for 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
fetchat 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 .docx03 — 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".
Recommended hosting pattern (lowest-disruption, all GA)
- 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/denoimage), 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.env→process.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 — it's just hosting (simple swap)
Clarification: Lovable's role in CARR is hosting only. The app is built in GitHub (carr-biosystems-hub) and the backend runs on Supabase. Lovable watches the repo via GitHub sync and serves the compiled React/Vite SPA on its CDN infrastructure with a custom domain (purchased by BioCreative). Lovable does not provide auth, data, or build tooling that would be lost.
Migration path (effort: S — days, not weeks):
- Build the React/Vite SPA (npm run build → dist/)
- Host the static output on Azure Static Web Apps, Container Apps (nginx container), or App Service
- Re-point the custom domain DNS to the new Azure host
- Lovable is removed entirely — no capability is lost
Reference pattern: BioCreative already hosts an identical React/Vite SPA for SpanCorr on a VPS (nginx:alpine container, Traefik TLS, SPA fallback) with zero Lovable involvement. The same pattern works on Azure.
The real migration discussion is authentication, not hosting:
- Currently: Supabase Auth manages user accounts (BC creates logins manually); supabase-js checks session + user_profiles.is_active client-side; RLS uses auth.uid() server-side.
- Post-migration: BW replaces this with Entra workforce auth — MSAL on the frontend, API middleware validating Entra JWT, user provisioning via Entra admin portal instead of the Supabase dashboard.
Alternative frontend frameworks (still rejected, but for the right reason):
- Power Apps + Dataverse: total rewrite into low-code; loses the React SPA. Reject.
- GitHub Spark: preview, built for short-lived "micro-apps" — unsuited to a 104K-contact production app. Reject.
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 .docx04 — 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/login— zero 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 — resolved (simple case)
All CARR app users are BW-internal. CARR is a BW subsidiary; everyone with a @carrbiosystems.com, @bw*, or @forsyth* email is already in BW's Entra workforce directory. The one non-BW user (BioCreative consultant) is being provisioned a BW login (Brian.Elbert@carrbiosystems.com). No External ID / CIAM / B2B-guest complexity is needed.
- RLS impact: replace auth.uid() with middleware that decodes the Entra workforce 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.
- Cost: $0 incremental for identity — all users are already licensed BW workforce accounts.
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.
- Identity: $0 incremental — all users are BW workforce (already licensed). No External ID MAU billing applies.
- Azure infra consumption ≈ $300–800/mo depending on DB tier/load (see
03for 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.
Recommended migration sequence (~8 weeks)
- Wks 1–2 — Foundation/Identity: Landing Zone + subscriptions/RGs; Entra workforce auth (all users BW-internal); repo → BW Enterprise GitHub; Actions + OIDC.
- Wks 3–4 — Data/Secrets: Key Vault (migrate secrets); Azure DB for PG in VNet; schema+data migrate; RLS refactor to session vars;
pg_cronjobs. - Wks 5–6 — Compute/API: containerize EFs → ACA; deploy JWT-validating API middleware; webhooks.
- 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 .docx05 — 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
01–04carry 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 (workforce) | ❌ replaced | M | All users are BW-internal (CARR is a BW subsidiary). Straight Entra workforce auth → MSAL on frontend. No External ID needed. 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 (hosting only — serves the React build) | Azure Static Web Apps or Container Apps (nginx) | ⚠️ re-point domain | S | Lovable is hosting only (code built in GitHub, backend on Supabase). Swap to SWA/ACA + re-point domain. No capability lost. See 03. |
| 13 | Frontend hosting (Lovable-hosted Vite SPA) | Azure Static Web Apps (note Vite build config) or Container Apps | ⚠️ | S | Static Web Apps; mind the Vite-not-CRA build-output gotcha. Proven pattern (SpanCorr runs identical React/Vite on nginx). |
| 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: host the React/Vite build output on Static Web Apps (or Container Apps with nginx). Lovable is hosting only — removing it is a domain re-point, not a rewrite.
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 React SPA are preserved as-is.
Highest-risk items (call these out to BW)
- Edge Functions (row 6) — 26 Deno functions; the rewrite-vs-containerize choice drives the timeline. Per-function census in
08_edge_function_census.md. - Auth replacement (row 9 → rows 11–12) — Lovable is hosting only (no capability loss when removed); the real gating work is replacing Supabase Auth with Entra (MSAL + middleware + user provisioning).
- Identity (row 9) — resolved. All users are BW-internal (CARR is a BW subsidiary); straight Entra workforce auth. Not a risk item.
Open questions
- Which compute host does BW standardize on (Container Apps vs App Service vs AKS)?
- Does BW mandate Power Platform for the frontend, or accept the existing React SPA on SWA/ACA?
- pg_cron retained, or all scheduling moved to Azure-native?