decision
ADR-0037: Staging-suffixed resources are production; add a sandbox tier
ADR-0037 (Accepted, 2026-07-21): Staging-suffixed resources are production; add a sandbox tier.
Status: Accepted
Date: 2026-07-21
Deciders: Seth
Context
No environment split was ever formally decided for MemberIntel V1’s GCP infrastructure. The -staging-suffixed Terraform resources (memberintel-api-staging, memberintel-db-staging, etc.) have been serving api.membersintel.com’s real, live traffic since before this was named or documented — several runbooks and CI config comments still asserted “there is no production environment,” which was already inaccurate by the time it was written down.
This left the team without a safe pre-prod tier: any infra or backend change had to be tested directly against what was, in fact, production. caseproof/innovations#369 scoped fixing this — not by renaming or migrating the live stack (real data-migration risk, zero user-facing benefit), but by formally naming today’s reality and adding a genuinely new, disposable tier for testing.
Decision
- Formally document that the
-staging-suffixed resources ARE production. No rename, no migration, no DNS change, no downtime.docs/runbooks/subdomain-map.mdanddocs/runbooks/01-deploy.mdwere updated to state this plainly instead of the stale “no production environment” framing. - Add a third
environmentvalue,sandbox, as a fully separate Cloud SQL + Cloud Run + VPC stack under*-sandbox.membersintel.comsubdomains — a place to break things safely without touching what’s live. Sandbox reuses production’s third-party API keys and OAuth client (deferred credential isolation to a future need, not this plan’s scope) and is explicitly not drift-checked nightly (.github/workflows/terraform-drift.yml’s matrix stays[production, staging]) — it’s meant to be disposable and frequently broken by design, so nightly drift-checking it has less value than for the tiers that matter, and would mostly generate noise. - Isolate the project-global GCP singletons (Artifact Registry, BigQuery datasets/sinks, the
tf-plandrift SA, the billing budget — resources with no environment suffix, shared across the one GCP project) into an optionalproject_globalsmodule, owned exclusively by the existing-staging-named state. A new environment’s Terraform state setsmanage_project_globals = falseso it never tries to recreate — or accidentally destroy — those shared singletons.
Consequences
Positive:
- A real, isolated pre-prod tier exists today, reachable without any risk to live traffic.
- No migration risk taken on — production’s actual serving infrastructure is untouched by this decision.
- The
project_globalssplit establishes the reusable pattern (manage_project_globals) that any future environment (including a genuineproductiontier) can adopt without re-deriving it.
Negative / costs:
- GCP console naming stays confusing (
-staging= production) indefinitely, unless a future migration project is explicitly chartered. - Sandbox has no drift detection — an out-of-band console change there can silently diverge from
infra/with no alert. Acceptable given sandbox’s disposable-by-design role; revisit only if it starts being treated as a long-lived shared resource rather than a personal test environment. - Sandbox shares production’s third-party API keys and OAuth client rather than having its own — a real blast-radius tradeoff (a sandbox-side key leak or quota exhaustion affects production’s own usage) accepted to keep this plan’s scope to infra isolation, not credential provisioning across every vendor.
Mitigations:
- Sandbox’s Terraform state is fully separate (its own GCS backend prefix) from both staging and any future production state, so a sandbox
terraform destroycan never reach the resources this decision protects. manage_project_globals = falseis enforced per-environment at the tfvars level, making an accidental attempt to re-manage the shared singletons from a new environment’s state visible interraform planbefore it could ever apply.
Alternatives considered
- Rename
-stagingresources to-production. Rejected: real Cloud SQL data-migration risk and a coordinated domain-mapping cutover, for zero user-facing benefit — the resources already correctly serve production traffic under whatever name they carry. - A genuinely separate, new production stack with a live cutover from the old
-stagingstack. Considered the more architecturally “correct” long-term fix, and has since been chartered as its own follow-on plan (docs/superpowers/plans/2026-07-16-three-tier-environment-architecture.md, caseproof/innovations#369’s Tasks 1-6 already merged as of this ADR’s date; the live cutover itself is still pending a dedicated session). Explicitly out of scope for the decision recorded here — this ADR covers only the sandbox-tier addition, not the eventual real production/staging split. - A drift-check leg for sandbox, mirroring production/staging’s nightly
terraform-drift.ymlmatrix. Rejected for now per the Decision section above; the plan’s own Task 7 recommendation was to skip and document rather than add machinery of unproven value for a disposable environment.