Scope: what ships beside the code
A piece of SaaS state is in scope when all four hold:
Out of scope, by design (Terraform’s or Pulumi’s job, see ROADMAP.md): the parent
objects these hang off (projects, tenants, applications, accounts, zones, buckets, clusters, flag definitions),
organisation, IAM and billing, networking, and anything that outlives every environment. Also out of scope: build
artifacts (source-map uploads), application data (seed rows belong to the app’s migration tool) and the deploy itself
beyond triggering and waiting on it (
vercel.deploy). Production lines are in scope only for the state a release
changes; long-lived production objects enter a plan as { keep: true } adoptions, never as creations.
Coverage matrix
Columns:- Side effect: what one environment adds, changes or removes.
- API shape: endpoint or CLI, auth, and the write model: object (one API object per item, created and deleted
on its own) or list (one array or map on a parent object, read-modify-write). For list shapes, concurrent
writers would lose updates; the op declares the parent object (
lockOn) and the engine serialises Sponson’s writers with a lock per object (ADR 0019), on top of any precondition the provider offers. - Generic?: could the declarative HTTP adapter described under Strategy express it? Y yes, P partly (the lifecycle needs more than request templates, noted), N no.
- Pri: how often it appears in preview-environment workflows. P0 most preview setups on that stack hit it; P1 common; P2 occasional. An estimate from public templates, integrations and docs, not a survey.
- Now: yes when a built-in op or a shipped recipe covers it; manual step when no API can
and a
manual.stepline tracks what a person does (ADR 0021), never counted as automated; no (no API) when nothing can.
covered_by names the op or recipe op that manages each one) and
the tables below are generated from it: edit the data, then run pnpm docs:gen.
Deploy targets and their environment variables
Databases with branching or per-PR instances
Auth callback and redirect allow-lists
Feature flags and targeting
Webhooks registered per environment
Email: sending domains, webhooks, test inboxes
Observability per release
DNS and preview domains
CORS and allowed origins
Queues, caches, background jobs
Search, payments test mode
Headless CMS content per preview
Backend platforms with preview deployments
Considered and not added
Checked on 2026-10-12 against the providers’ docs, and left out of the matrix because one of the four scope tests fails. A row is added when that changes; none of these is counted, covered or not.Secret managers (read side: secret sources, not side effects)
Secret sources resolve{ secret } references; they create nothing, so they are counted separately. The built-in ones
are listed in docs/plan-format.md.
The write side (a per-PR Doppler branch config,
POST /v3/configs) is a side effect; it is P2 and not counted above.
Current coverage
Every number below is computed from coverage.yaml bypnpm docs:gen; a row counts as covered when
a registered op or a shipped recipe manages it.
Counted over the 67 side-effect rows of the matrix (rows marked n/a have nothing to manage). Weights: P0 = 3, P1 = 2, P2 = 1 (9 P0, 18 P1, 40 P2; total weight 103).
Covered: Vercel (
vercel.env, vercel.deploy); Netlify (netlify.env); Cloudflare Pages / Workers (cloudflare.pages_env); Render (recipe render.env_var); Railway (recipe railway.variable); Fly.io (recipe fly_machines.app, recipe fly.secret); Heroku (recipe heroku.config_var); AWS Amplify (recipe amplify.branch, recipe amplify.branch_variable); Google Cloud Run (cloud_run.revision); Azure Static Web Apps (recipe azure_swa.build_app_setting); DigitalOcean App Platform (digitalocean.app); Firebase Hosting (recipe firebase_hosting.channel); Neon (neon.branch); Supabase (supabase.branch); PlanetScale (planetscale.branch, planetscale.password); Turso (recipe turso.database_branch); Xata (xata.branch); MongoDB Atlas (recipe mongodb_atlas.database_user); CockroachDB Cloud (recipe cockroachdb.database); Prisma Postgres (recipe prisma.database, recipe prisma.database_copy); Clerk (clerk.redirect_allow); Auth0 (recipe auth0.callback_url, recipe auth0.logout_url, recipe auth0.web_origin, recipe auth0.allowed_origin); Supabase Auth (supabase.auth_redirect); Firebase Auth (recipe firebase.authorized_domain); WorkOS (recipe workos.redirect_uri); Stytch (recipe stytch.redirect_url); Okta (recipe okta.redirect_uri, recipe okta.post_logout_redirect_uri, recipe okta.trusted_origin); Kinde (recipe kinde.callback_url, recipe kinde.logout_url); LaunchDarkly (launchdarkly.flag_target); PostHog (recipe posthog.release_condition); Statsig (recipe statsig.gate_rule); GrowthBook (recipe growthbook.force_rule); Unleash (recipe unleash.flag_strategy); Flagsmith (recipe flagsmith.environment); ConfigCat (recipe configcat.targeting_rule); Split (Harness FME) (recipe split.targeting_rule); Stripe (recipe stripe.webhook_endpoint); GitHub (recipe github.repo_webhook); Svix (as a sender) (recipe svix.endpoint); Twilio (recipe twilio.twiml_app); Ably (recipe ably.webhook_rule); Resend (recipe resend.api_key, recipe resend.webhook); Postmark (recipe postmark.server, recipe postmark_server.webhook); SendGrid (recipe sendgrid.event_webhook, recipe sendgrid.parse_setting); Mailgun (recipe mailgun.webhook, recipe mailgun.route); Sentry (recipe sentry.release, recipe sentry.deploy); Datadog (recipe datadog.synthetic_api_test); Honeycomb (recipe honeycomb.marker); New Relic (recipe newrelic.deployment); Rollbar (recipe rollbar.deploy); BugSnag (recipe bugsnag.build); Cloudflare DNS (recipe cloudflare.dns_cname); Vercel domains (recipe vercel.project_domain); Firebase Storage (GCS bucket) (recipe gcs.bucket_cors); S3 / R2 (recipe s3.bucket_cors); Sanity CORS (recipe sanity.cors_origin); Upstash Redis (recipe upstash.redis_database); Upstash QStash (recipe qstash.schedule); Inngest (recipe inngest.sync); Algolia (algolia.index); Stripe test-mode objects (recipe stripe.product, recipe stripe.price, recipe stripe.coupon); Contentful (contentful.environment, contentful.api_key_access); Sanity (recipe sanity.dataset); Convex (recipe convex.preview_deployment, recipe convex.deploy_key).
Automated coverage counts only rows an op manages without a person. Rows no API reaches (manual, or a manual.step line that records the step a person does; weight 5): Google OAuth clients (no public API for standard OAuth web clients (re-checked 2026-10-12): Google’s web-server guide configures redirect URIs on the Console Clients page only (developers.google.com/identity/protocols/oauth2/web-server), the IAM oauthClients API and gcloud iam oauth-clients are ‘only for Workforce Identity Federation’ (docs.cloud.google.com/iam/docs/workforce-manage-oauth-app, docs.cloud.google.com/iam/docs/reference/rest/v1/projects.locations.oauthClients), and the IAP OAuth Admin API ‘was shut down on March 19, 2026’ and never allowed updating redirect URIs (docs.cloud.google.com/iap/docs/programmatic-oauth-clients)); Clerk webhooks (Clerk’s API manages the Svix app, not its endpoints (re-checked 2026-10-12): the Backend API spec (github.com/clerk/openapi-specs, bapi/2025-11-10.yml and 2026-05-12.yml) has only CreateSvixApp, DeleteSvixApp and GenerateSvixAuthURL (‘Generate a new URL for accessing the Svix’s management dashboard’), the private-beta Platform API (platform/beta.yml) has no webhook routes, the clerk api ls webhook route the webhooks overview points agents to (clerk.com/docs/guides/development/webhooks/overview) lists those same three Backend API routes, clerk webhooks is a local relay and verifier that makes ‘No Clerk API calls’ (github.com/clerk/cli, commands/webhooks), and Clerk documents no Svix API token for its app (a portal-URL token is undocumented)); Stripe sandboxes (no public API for sandboxes in your own account (re-checked 2026-10-12): docs.stripe.com/sandboxes/dashboard/manage creates and deletes them in the Dashboard only, up to five per account, the Claimable Sandboxes API (docs.stripe.com/sandboxes/claimable-sandboxes) makes anonymous sandboxes that no existing account owns, for coding platforms that set up Stripe for their users, needs a Stripe App that Stripe reviews, uses a preview API version and has no delete (unclaimed ones are deleted after 60 days), and stripe sandbox create (docs.stripe.com/cli/sandbox) provisions an account-less sandbox only when the CLI is logged out (‘If you’re already logged in, sandbox create opens the sandbox management page in your Stripe Dashboard’) that expires after 7 days). They cap coverage at 64 rows, 98 of 103 weighted (95.1%).
Recipe candidates, rows the generic adapter can express fully (Y) that nothing covers yet: 0 ().
Strategy
Two kinds of adapter close the gap. First-class adapters (TypeScript, one sim routes file, scenarios) for rows whose lifecycle is more than create/read/delete: asynchronous provisioning (branches, Supabase previews), an external event barrier (Netlify and Railway deploys, likevercel.env), outputs that exist only once behind a multi-step lifecycle (PlanetScale
password; a once-only output of a plain object, such as Stripe’s webhook secret, is a recipe), instruction-based diffs (LaunchDarkly), GraphQL, or non-bearer signing (SigV4).
The generic HTTP adapter and its recipes (adapter: http, ADR 0017 and
ADR 0020) for the long tail where each item is a URL, a rule or a record and the lifecycle is
plain CRUD. A row counts as covered once a recipe for it ships: a verified, tested, documented spec a
plan line names in one line, not a request template every team writes. The rows marked Y that no recipe covers
yet are listed under Current coverage.
What adapter: http must support to cover the P0/P1 rows
- Provider block with credentials by reference: base URL, auth as bearer, custom header(s) (
STATSIG-API-KEY, Algolia’s two headers), Basic, a token from an OAuth2 client-credentials exchange (Auth0, Kinde, Okta), or a token from a command (gcloud auth print-access-tokenfor Firebase/GCS), SigV4 or Digest. Credentials are environment variable names, never values. Done (ADR 0017 and its amendment), with XML bodies and GraphQL requests. - Two write models: object (create, read by id, update, delete) and list-on-parent (read the parent, find the
item in an array, write the parent back). List mode must handle an array at a JSON path, a delimited string
(Supabase
uri_allow_list), and a keyed map with merge-patch delete-by-null (Cloudflare Pagesenv_vars, Heroku config vars). - Request templates for read, create, update and delete: method, path, query (
updateMask,site_id), body, with${ctx.*}, params,{ from }and{ secret }substitution; body encodings JSON, form-encoded (Stripe), JSON Patch, merge-patch, and a customContent-Type(LaunchDarkly semantic patch). - Response mapping by JSON path: the id, the fields compared for drift (hashed in the same canonical form as
ResourceRecord.hash), and named outputs, each markablesensitive. - Identity and lost-response recovery: a resource key template, and a find request (list + match predicate on a
field such as
urlorname) so a create whose response was lost is found and claimed instead of duplicated;Idempotency-Keyfrom the ledger key where the provider honours it (Stripe), client-chosen ids where it takes them (Svixuid, QStashUpstash-Schedule-Id). - Preconditions and serialization for list mode: send the read version back when the provider has one (ETag /
If-Match, GCSifMetagenerationMatch), re-read after write to confirm the item landed, and declare the parent object (lockOn) so the engine’s per-object lock serialises Sponson’s writers (ADR 0019). Without this, two PRs editing one Auth0 application lose writes. - Status classification overrides: which statuses mean “already exists” (re-read and claim), “gone” (success on
delete), “busy” (retry), on top of
packages/adapters/src/http.ts’s defaults; plus pagination styles (cursor,nextlink, page number). - Destroy modes:
delete,updateto an archived state (Stripe pricesactive=false), remove-from-list, orleave(Sentry releases are history).deleteandleaveexist (destroy: keep); a recipe op sets the default (destroy: keep, orneverwhen the provider cannot delete; ADR 0020, amendment). - Simple polling: after create, poll a URL until a JSON path equals a value, with a timeout (Algolia tasks, Turso or Prisma readiness). Long multi-step lifecycles stay first-class.
- Scope listing for drift and
sponson init: a list request plus a filter (name prefix, URL host) so unmanaged items are reported and adoptable. - Once-only outputs: an output that is only in the create response must be declared as such, so the engine
feeds it to dependent lines in the same run and refuses a later run that needs it (instead of reading an empty
value). Needed for Stripe webhook secrets, API keys and DB passwords even in first-class adapters. The engine
side exists:
OutputSpec.once(ADR 0018), first used byplanetscale.password;http.resourcedeclares one as{ path, sensitive: true, once: true }(ADR 0020, amendment;stripe.webhook_endpoint). Archiving destroys (item 8) aredelete: { method: POST, body: … }(stripe.price). - Sim support: a generic sim route set driven by the same declaration, so every
httpline gets the chaos and drift scenarios the built-in adapters get.
The next eight first-class adapters
Ranked by weighted rows covered, then by how much they need more thanadapter: http.
With these eight plus recipes for the Y rows, the remaining rows are
Fly.io, Amplify, Cloud Run, Azure Static Web Apps, DigitalOcean, Xata, Algolia (all P1/P2 with partial generic fit or
SigV4 needs; MongoDB Atlas and CockroachDB turned out to be recipes) and the three rows with no API.
Sources
The Netlify, Cloudflare Pages, Fly.io, Supabase Auth, Turso, Prisma Postgres, Clerk webhook, Firebase, WorkOS, Stytch, Kinde, Google OAuth, Statsig and Stripe sandbox facts were re-checked against these pages on 2026-10-11. The rest come from the providers’ docs as the author knew them and were not re-fetched for this document; treat them like (unverified) until an adapter’s verification table (docs/api-verification.md) covers them.- Vercel REST API: vercel.com (env, deployments, project domains)
- Netlify API (OpenAPI): github.com; env vars: docs.netlify.com
- Cloudflare Pages project update: developers.cloudflare.com; DNS records: developers.cloudflare.com; R2 CORS: developers.cloudflare.com
- Render API: api-docs.render.com
- Railway public API: docs.railway.com
- Fly.io secrets: fly.io; Machines API secrets change: community.fly.io
- Heroku Platform API: devcenter.heroku.com
- AWS Amplify
UpdateBranch: docs.aws.amazon.com - Cloud Run revision tags: cloud.google.com
- Azure Static Web Apps app settings: learn.microsoft.com
- DigitalOcean App Platform: docs.digitalocean.com
- Neon API: api-docs.neon.tech
- Supabase Management API: supabase.com; auth config: supabase.com; branching: supabase.com
- PlanetScale API: planetscale.com (OpenAPI document; checked call by call for the adapter on 2026-10-11, see api-verification.md)
- Turso branching: docs.turso.tech; create database: docs.turso.tech
- Xata branching: xata.io
- MongoDB Atlas Admin API: mongodb.com
- CockroachDB Cloud API: cockroachlabs.com
- Prisma Postgres Management API: prisma.io; connections: prisma.io
- Clerk Backend API: clerk.com; Clerk’s Svix operations: pkg.go.dev
- Auth0 Management API, update a client: auth0.com
- Firebase / Identity Platform
projects.updateConfig: cloud.google.com - WorkOS redirect URIs: workos.com
- Stytch PWA redirect URLs: stytch.com
- Okta Apps API: developer.okta.com; Trusted Origins: developer.okta.com
- Kinde callback URLs: docs.kinde.com; Management API: docs.kinde.com
- Google OAuth clients (no redirect-URI API): discuss.google.dev; IAM OAuth clients: cloud.google.com
- LaunchDarkly semantic patch: launchdarkly.com
- PostHog feature flags API: posthog.com
- Statsig Console API gate rules: docs.statsig.com
- GrowthBook REST API: docs.growthbook.io
- Unleash Admin API: docs.getunleash.io
- Flagsmith Admin API: docs.flagsmith.com
- ConfigCat Public Management API: configcat.com
- Split Admin API: docs.split.io
- Stripe webhook endpoints: docs.stripe.com; idempotent requests: docs.stripe.com; sandboxes: docs.stripe.com
- GitHub repository webhooks: docs.github.com
- Svix endpoints: api.svix.com
- Resend API: resend.com
- Postmark API: postmarkapp.com
- SendGrid webhooks: twilio.com
- Mailgun API (OpenAPI): documentation.mailgun.com; account webhooks: documentation.mailgun.com; routes: documentation.mailgun.com
- Twilio Applications (TwiML Apps): twilio.com; OpenAPI: github.com
- Ably Control API: ably.com; OpenAPI: ably.com
- New Relic change tracking: docs.newrelic.com
- Rollbar deploys: docs.rollbar.com
- BugSnag build API (its CLI’s payload and endpoints): github.com; options: github.com
- Priority evidence for the rows added on 2026-10-12: npm downloads in the last month, api.npmjs.org (one request per package; each row’s
evidencein coverage.yaml) - Sentry releases: docs.sentry.io
- Datadog synthetics API: docs.datadoghq.com
- Honeycomb markers: docs.honeycomb.io
- GCS bucket CORS: cloud.google.com
- Amazon S3
PutBucketCors: docs.aws.amazon.com - Upstash Developer API: upstash.com; QStash schedules: upstash.com
- Inngest branch environments: inngest.com
- Algolia copy/move index: algolia.com
- HashiCorp Vault KV v2: developer.hashicorp.com
- Infisical API: infisical.com
- Azure Key Vault get secret: learn.microsoft.com
- 1Password Connect API: developer.1password.com
- Bitwarden Secrets Manager CLI: bitwarden.com
- Doppler configs API: docs.doppler.com
- Contentful environments and API keys: contentful.com (re-checked 2026-10-12, see api-verification.md)
- Sanity Projects API (datasets, CORS origins): sanity.io
- Convex Management API: docs.convex.dev; OpenAPI: api.convex.dev
- Firebase Hosting channels: firebasehosting.googleapis.com; preview channels: firebase.google.com