Multi-tenant SaaSbaseline-v2 · Cloudflare + Supabase · ~75 minUse this baseline
baseline-v2Cloudflare + Supabase~75 min

Multi-tenant SaaS

The same thirteen Workers, the same Next.js console, the same Terraform data plane on Postgres and the same guardrails as Lumen — carried on a separate release line, with the Sourceplane brand baked into the identity defaults a new product inherits.

44components
12bounded contexts
3environments
~75 minutesto live

Inherited from Lumen's measured instantiation — the same fleet, phases and long pole. Replaced with this baseline's own figure once measured.

The arc

Eight phases, in four arcs.

Each phase applies its slice, lands it as a pull request, watches the deployment converge, then verifies that what it built is answering. Every phase is idempotent.

ARC 01

The repository baseline

Before anything can be built there has to be somewhere to build it, and something that knows how. These two phases create the repository and land the shared foundation every later component compiles against.

01

Scaffold

The GitHub repository, created and linked to your workspace, carrying its declared intent, its CI, its bootstrap flows, its tooling, and your product identity applied throughout.

Verified byThe repository is pushed and workspace-linked.
02

Foundation

Nine shared packages: contracts, database, policy engine, SDK, CLI and the rest.

Verified byEvery package's verify lane is green on the pull request and again on main.
ARC 02

The infrastructure baseline

The data plane arrives as components with the same lifecycle as your code — planned on the pull request, applied on merge, with credentials brokered per run rather than parked in a secret store.

03

Infrastructure

The database, the connection layer, the edge key-value namespace, and the migration component — each a Terraform or migration component with its own lane.

Verified byThe wiring secrets are published and readable by the components that need them.
ARC 03

The product surface

The part your customers touch: the bounded contexts, the single public API in front of them, and the console. Each phase deploys to stage and prod and proves the result over HTTP before the next begins.

04

Workers

The twelve bounded-context Workers, with their service bindings and entitlement gates.

Verified byConvergence is green and every binding is restored.
05

Edge

The single public API surface in front of the fleet — idempotency, rate limiting, actor resolution.

Verified byThe health endpoint answers 200 on stage and prod.
06

Console

The Next.js console on Workers and Static Assets, per environment.

Verified byThe console and the edge both serve live.
ARC 04

Domain and documented state

The last arc turns a running deployment into an operable one: your domain in front of it, and a written record of what is live — generated from what was actually probed, not from what was intended.

07

Domain

Your custom domain, attached per environment. Optional — workers.dev URLs work without it.

Verified byConvergence is green and the zone resolves.
08

Docs

A deployment manifest and an operating contract, written into the repository.

Verified byThe committed manifest matches probed reality.

Supabase project creation is the long pole in phase 03 — budget five to seven minutes per environment.

Components

Everything is a component.

Infrastructure, migrations and test suites declare themselves the same way a service does — a manifest naming an owner, a golden path, and the environments it subscribes to.

Data plane & migrations

Supabase Postgres per environment, pooled into the Workers runtime through Cloudflare Hyperdrive, with an idempotency KV namespace at the edge.

supabase

Provisions the stage and prod Postgres projects and publishes their credentials.

cloudflare-hyperdrive

Pooled Postgres connectivity for the Workers runtime.

cloudflare-kv

The KV namespace backing the edge idempotency and rate-limit stores.

cloudflare-domain

The zone, and the custom-domain attach for each environment's console.

db-migrate

Applies the migration set to each live environment — planned on the pull request, applied on merge.

Bounded contexts

Twelve private Workers, each owning its own schema and reachable only over service bindings.

identity-worker

Authentication runtime: sessions, token mint and refresh, magic-link and OAuth flows.

membership-worker

Organizations, members, roles, invitations and join requests — the tenancy spine.

projects-worker

Projects and their environments inside a workspace.

policy-worker

Authorization decisions. Every permission question in the product resolves here, against one engine.

events-worker

The event stream and the audit trail — streams, groups, dead letters, and a scheduled retention sweep.

notifications-worker

Notification rules, channels and delivery for the whole platform.

webhooks-worker

Outbound webhook endpoints, subscriptions and delivery attempts, with signed payloads.

billing-worker

Plans, checkout, entitlements. Private by construction — reachable only over a service binding, never from the edge.

metering-worker

Usage recording and quota checks behind the entitlement gates.

config-worker

The read surface for configuration, secrets and feature flags.

integrations-worker

Provider connections, the GitHub App installation, the inbound delivery inbox, and the repo-scoped token broker.

admin-worker

Internal support and administration diagnostics — every call audited.

The public surface

api-edge

The single public API: actor resolution, idempotent replay, rate limiting, and a typed facade per bounded context.

web-console-next

The Next.js console on Workers and Static Assets, deployed per environment behind your domain.

Shared packages

Compiled into every Worker, so a contract change is a compile error rather than a production surprise.

contracts

Wire types and schemas. Every worker, the SDK and the console read the same file.

db

Schema, repositories and the migration set, one module per bounded context.

shared

Cross-cutting runtime helpers shared by every Worker.

policy-engine

The authorization engine, the predicate evaluator for secret conditions and flag targeting, and deterministic flag bucketing.

sdk

Runtime-agnostic TypeScript SDK for the control-plane API.

cli

First-class CLI over the same API, sharing the SDK's types.

notifications-client

Typed client for emitting notifications from any context.

webhook-verifier

Signature verification, published for your customers to use.

testing

Shared fixtures and harnesses the per-context test packages build on.

Test packages

16 suites, each declared as a component beside the context it covers.

admin-worker-testsapi-edge-testsbilling-worker-testsconfig-worker-testscontracts-testsdb-testsidentity-worker-testsintegrations-worker-testsmembership-worker-testsmetering-worker-testsnotifications-client-testsnotifications-worker-testspolicy-engine-testspolicy-worker-testsprojects-worker-testsweb-console-next-tests
Checks

The checks belong to the golden path.

A component's manifest names a composition and nothing else about its pipeline. The composition owns what runs on a pull request, what runs on merge, and what each is allowed to touch.

type: turbo-package

Shared package

The workspace packages every Worker compiles against — contracts, database, policy engine, SDK, CLI — and the per-context test suites that sit beside them.

On every pull requestprofile: quick-check
  • setup-pnpm · setup-node
  • install workspace (frozen lockfile)
  • verify package structure

Packages are compiled transitively rather than in a lane of their own: every Worker's build builds its workspace dependencies first, so a broken contract turns the lane of every component that imports it red.

On merge to mainprofile: quick-check
  • the same lane, re-asserted on main

Shared packages have nothing to deploy — they are consumed by the components that do.

type: terraform

Terraform data plane

Every provisioned resource is a component with the same lane. Plans are visible on the pull request; only merges apply.

On every pull requestprofile: plan-only
  • setup terraform
  • broker provider credentials
  • resolve environment + context
  • fmt
  • init
  • select workspace
  • validate
  • plan

No lock is taken, so a plan can never block a concurrent apply.

On merge to mainprofile: apply
  • everything the pull-request lane ran
  • apply
type: db-migrate

Database migrations

Migrations are a component, not a deploy-script afterthought: what a merge will apply is reported on the pull request that proposes it.

On every pull requestprofile: plan
  • setup-pnpm · setup-node
  • install workspace
  • build
  • broker credentials
  • plan pending migrations

Authenticates and reports pending migrations without touching the database.

On merge to mainprofile: apply
  • everything the pull-request lane ran
  • apply pending migrations
type: cloudflare-worker-turbo

Cloudflare Worker

A bounded-context Worker built through Turborepo, verified offline on every pull request and deployed per environment on merge.

On every pull requestprofile: verify
  • setup-pnpm · setup-node
  • install workspace (frozen lockfile)
  • verify component structure
  • render bindings from the committed fixture
  • build
  • typecheck
  • wrangler deploy --dry-run

Runs with no cloud credentials and no state backend — a pull request cannot obtain them.

On merge to mainprofile: deploy
  • everything the pull-request lane ran
  • resolve binding IDs from the wiring manifest
  • re-render the live wrangler config
  • pre-deploy hook
  • migrate
  • deploy
  • hydrate runtime secrets (partial seed hard-fails)
  • smoke the deployed URL

Selected only when the trigger is a push to main, and gated on a clean git tree plus approval.

type: cloudflare-workers-assets-turbo

Next.js console on Workers + Static Assets

The console builds through OpenNext into a Worker entrypoint plus an assets directory, deployed together in one upload.

On every pull requestprofile: verify
  • setup-pnpm · setup-node
  • install workspace (frozen lockfile)
  • verify component structure
  • build
  • verify build output
  • wrangler deploy --dry-run

Offline: build output is verified structurally, then dry-run.

On merge to mainprofile: deploy
  • everything the pull-request lane ran
  • deploy Worker + assets
  • smoke the console and the edge /health endpoint

Push-to-main only. The smoke step re-probes until the edge serves the new version, so a green check means live.

Merge checks & deploy

Every commit is planned, not scripted

CI runs no raw package-manager, Wrangler or Terraform commands. It compiles the repository's declared intent into a plan scoped to what actually changed, and that plan becomes the job matrix — one named check per component in scope.

Pull request

On a pull request

Each component in scope runs its composition's verify lane, with no remote state and no cloud credentials. Superseded runs on the same branch are cancelled, because each push re-plans from scratch.

Merge

On merge to main

The same plan runs with deploy profiles selected by trigger, coordinated through remote state so concurrent components take leases rather than collide. Each merge to main gets its own concurrency group: a superseded run would otherwise leave its components unconverged.

dev

Verify only

No provisioned data plane, by design. It exists so every lane can be dry-run per environment.

stage

Live

Full data plane. Every merge to main converges it first.

prod

Live

Full data plane, promoted behind stage in the same run.

The guardrail baseline

Load-bearing on day one.

Present from the first commit, in every bounded context, and the pipeline is what stops it eroding.

AUTHORIZATION

One place decides who may do what

Account, organization and project roles resolve through a single policy engine, exposed as an authorization API, an effective-permissions read, and role-assignment validation. The Worker, the SDK and the console link the same engine, so a permission cannot mean one thing in the API and another in the UI.

AUDIT

An audit trail that ages out on purpose

Mutations land in the event stream with actor, subject and request id. Streams, groups and dead letters are first-class, and a scheduled retention sweep runs off-peak in batched, capped, idempotent deletes — so retention is a policy you set, not a table that grows until someone notices.

SAFE RETRIES

Idempotency at the edge, not per endpoint

Every unsafe method carrying an Idempotency-Key is resolved against a durable replay store before the request reaches a context Worker; a hit replays the stored response without re-executing. A malformed key is rejected before the store is touched, and a store outage degrades to execute-once rather than returning a 5xx.

ABUSE

Rate limits with two independent buckets

A token bucket per organization and a second per identity fingerprint, with anonymous traffic falling back to route family plus source IP so a single caller cannot dodge the limit by dropping its token. It fails open with a logged warning: a limiter outage must not become an outage.

CONFIG

Secrets, config and flags on one lattice

The set of legal scopes is closed and ordered, so resolution is a short walk with a printable order instead of a search over every combination of pinned dimensions. Flag bucketing is deterministic and shared by the Worker, the SDK and the console — three approximations of a hash agree right up until a user reports flicker.

CI POSTURE

A pull request cannot reach production

Verify lanes are offline by construction: bindings render from a committed fixture, the deploy is a dry run, and no state backend is contacted. The deploy profile is selectable only when the trigger is a push to main, and carries a clean-tree requirement and an approval gate. This is a property of the pipeline, not a convention reviewers uphold.

DATABASE

Migrations are reviewed before they run

The migration component plans on the pull request — authenticating and reporting exactly what is pending — and applies only after merge. What will happen to the database is on the diff that proposes it.

SECRETS

Nothing credential-shaped is committed

Worker configuration is rendered at deploy time from the wiring manifest, never checked in with real resource IDs. Runtime secrets hydrate after the deploy from escrow documents, and a partial seed fails the lane loudly instead of leaving a Worker half-configured.

PROOF

Green means answering, not uploaded

Every deploy lane ends in a smoke step that probes the deployed URL and the edge health endpoint, retrying against propagation. A Worker upload returns before every edge serves the new version, so the check that matters is the one made afterwards.

Build Multi-tenant SaaS in your workspace.

Connect Cloudflare + Supabase, name a repository, and press one button.