> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0401 — GCP: a billing-detach spend stop, Cloud SQL backups with point-in-time recovery, and deploy roles narrower than owner

> Proposed · 2026-10-26

> [!NOTE]
>
> Proposed. It applies [0172](https://docs.stackra.figentra.com/adr/0172-aws-accounts-primary-region-and-cloud-cost-controls.md)
> decision 5 and [0186](https://docs.stackra.figentra.com/adr/0186-backups-and-disaster-recovery.md) to GCP and answers
> [0354](https://docs.stackra.figentra.com/adr/0354-two-clouds-aws-and-gcp-and-one-aws-account-per-product-region.md) open question 1
> for the database only. The backup project and the project-per-product layout (0354 open question
> 2\) stay owner decisions. Linear STA-730.

## Context

GCP budgets alert but take no action, so the non-production stop of 0172 had no GCP form. The GCP runtime was
undecided, which left Cloud SQL without the backups and point-in-time recovery 0186 requires. The deploy service account
held `roles/owner`, and a long project name could make a service account id GCP refuses only at apply time.

## Decision

1. **Spend stop.** When `providers.gcp.budgets.stopNonProdAt` is set, a project production does not run in gets a Cloud
   Function on its budget topic that detaches the project's billing once the cost reaches that percentage. Its own
   service account holds only `roles/billing.projectManager` on the project; the function is internal-ingress only.
   Production is never detached.
2. **Cloud SQL in the runtime stack.** `gcpRuntime` creates the environment's Postgres server from `shared.database`
   (tier, version, `highAvailability`), always with daily backups stored in the server's own region, keeping
   `backups.retentionDays` backups, and point-in-time recovery for `backups.pitrDays` when `backups.pitr` is on. The
   server is deletion-protected, joins the runway's VPC on a private IP (or is reachable only through the connector
   over TLS) and uses the deployment's data key when one is passed.
3. **Deploy roles.** The infra service account holds the predefined roles of the resources the programs manage
   (`DEPLOY_ROLES`), never `roles/owner`. `projectIamAdmin` remains, so the trust stays the GitHub environment's.
4. **Service account guard.** Every service account id a deployment makes is checked against GCP's 30 characters while
   the runway is read, so `stackra validate` fails before Pulumi runs.

## Consequences

* A sandbox project stops spending at its threshold without an operator; re-linking billing is an owner step.
* A new resource kind in infra-gcp adds its role to `DEPLOY_ROLES` in the same change.
* The budget on the billing account needs `roles/billing.costsManager` granted by the owner.

## Enforcement

`packages/infrastructure/infra-gcp/src/node/functions/__tests__/{spend-stop,gcp-runtime,ci-access,gcp-baseline}.function.test.ts`;
`packages/infrastructure/infra-gcp/src/core/functions/__tests__/{service-account-id,gcp-runtime-config}.function.test.ts`.
Planned — the spend stop exercised on a sandbox project by the owner.

## Supersedes and sources

Supersedes: none. Sources: STA-730; ADR 0354 OQ1; JUR-215; `notes/handover/build-wave10-2026-10-25.md` (infra-gcp).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.