---
title: "QA Environment"
slug: gcp-env-qa
section: GCP Infrastructure
status: stable
description: "The QA environment is the GCP project `quality-assurance-496022` (project number 735345320299, display name \"Quality Assurance\", under the organization)."
last-updated: 2026-10-07
---

# QA Environment

## Purpose and scope

The QA environment is the GCP project `quality-assurance-496022` (project number 735345320299, display name "Quality Assurance", under the organization). It is the middle step of the `development` to `qa` to `prod` path and holds the QA copy of the Globus back office, the Host Portal, the applicant portal (PPPToo) with its Keycloak, WildPacks, n8n and a small pool of branch sandbox services. See [Git Branching & Release Model](/docs/git-branching-release-model) and [Onboarding: CI/CD & Deployment](/docs/onboarding-cicd-deployment) for how code moves into QA, and [GCP Overview](/docs/gcp-overview) for the other projects.

The Terraform code is `environments/qa` in `alliance-gcp-infra`, with remote state in the bucket `qa-tfstate-quality-assurance-496022` (prefix `alliance-gcp-infra`, `environments/qa/backend.tf`). The default region in `terraform.tfvars` is `us-central1`.

## Rebuilt net-new in 2026-07

QA was wiped and rebuilt from a bare skeleton starting 2026-07-12, including both of its old Terraform state buckets. The header of `environments/qa/main.tf` lists the rebuild order: project services, network, WIF pools and firewall, one service account per workload, Cloud SQL, secrets, Artifact Registry, GKE, Redis, Cloud Run, load balancing and DNS, Cloud Build, buckets, then verify and cutover. The rebuild removed known anti-patterns of the old QA (borrowed service-account identities, an `ocr-service` co-located in `globus-qa`, anonymized `secret-alias-N` secrets, a dead Cloud Build trigger, a load balancer pointing at a deleted service). The procedure is `docs/runbooks/qa-wipe-checklist.md`: plan review and sign-off, one domain per PR, dump secret values before deleting, apply, re-upload values, manual Workload Identity rewiring for GKE workloads, then verify. See [GCP Runbooks](/docs/gcp-runbooks).

The root `README.md` is stale on this point: it still describes QA as mid-rebuild at "PR-0 complete" and says development and production are not under Terraform. The `.tf` files, the workflows and the drift data show QA is fully populated and managed.

## What runs here

Workloads, from the live snapshot (read-only listing, names and settings only):

- GKE Autopilot cluster `globus-cluster-qa` (`us-central1`, regular release channel) running the Globus app (namespace `globus-qa`) and n8n (namespace `n8n`), each behind a GKE ingress load balancer. In-cluster manifests are not owned by this repo.
- Cloud Run in `northamerica-northeast1`: `applicant-keycloak`, `ppp2-participant-server-qa`, `ppp2-participant-client-qa`.
- Cloud Run in `us-central1`: `host-portal-qa-server`, `host-portal-qa-client`, `wildpacks-qa`, `cloudbuild-teams-notifier`, `db-query-proxy-qa`, and the three branching-sandbox slots `sandbox-web-qa-1` to `sandbox-web-qa-3` (minScale 0).
- Cloud SQL (all private IP, deletion protection on): `globus-db-qa` (Postgres 16), `n8n-db-qa` and `keycloak-db-qa` (Postgres 15). `keycloak-db-qa` reports `ENCRYPTED_ONLY`, which matches the Terraform setting that [Disaster Recovery Plan](/docs/disaster-recovery-plan) asks to be confirmed (its table still lists `requireSsl: false` from an earlier audit).
- Memorystore Redis `n8n-redis-qa` (basic tier, 1 GB), Artifact Registry repos, and one Cloud Build trigger (`wildpacks-qa`, branch `^qa$`, in `us-central1`).

Host Portal is served by Cloud Run behind an external managed load balancer (`host-portal-qa-*`, `host-portal-qa-cert`); `qa.hosts.allianceabroad.com` routes through the `host-portal-qa-http-fr` and `host-portal-qa-https-fr` forwarding rules to backend services that target the Cloud Run network endpoint groups. In `host-portal-qa-cloud-run.tf`, `GLOBUS_BASE_URL` is set to `https://qa-1.globus.allianceabroad.com`. Per PR #307, a GKE copy of Host Portal (namespace `host-portal-qa` on `globus-cluster-qa`) and its Cloud Build trigger `host-portal-qa` were deleted out of band on 2026-09-28, and the matching Terraform (`google_service_account_iam_member.host_portal_qa_runtime_wi` and `module.trigger_host_portal_qa`) was removed in the same cleanup; the deletion itself was not verified live. `host-portal-qa-runtime` (the Cloud Run runtime identity) and the `host-portal-qa-ip` address were intentionally kept. The snapshot still shows a second managed certificate for the same hostname on the Globus GKE ingress proxy that was PROVISIONING at snapshot time. [Repo Inventory](/docs/repo-inventory) lists the Host Portal repository and notes its production move to Cloud Run.

## How things get deployed

- Infrastructure: `terraform-apply-qa` runs on every push to `main` that touches `environments/qa/**` or `modules/**`. It authenticates as `terraform-tf@quality-assurance-496022` through the `github-actions-apply-qa` WIF pool, which is restricted to this repo on `refs/heads/main`. A destroy-count circuit breaker (`DESTROY_THRESHOLD: 3`) stops the apply when a plan proposes more deletions. It can also be dispatched with a `targets` list to scope a run. PRs get plan-only runs (`terraform-plan-pr`) using the viewer service account `terraform-ci-viewer-qa` through the `github-actions-iac` WIF pool; that account holds `roles/storage.objectUser` on the state bucket so it can take the state lock. The plan step always ends with exit code 0 and reports its result (including `Plan failed`) in a PR comment, so a green check does not by itself mean the plan succeeded. See [GCP Terraform Workflows](/docs/gcp-terraform-workflows).
- Application images: deployed by each application repo's own CI or Cloud Build (for example the `deploy-qa.yml` of HostPortalPrototype, the `wildpacks-qa` trigger). Terraform creates the Cloud Run services with a bootstrap image and `lifecycle.ignore_changes` on the image so an apply never reverts a real deploy (see `host-portal-qa-cloud-run.tf`).
- n8n: `null_resource.n8n_baseline_deploy_qa` runs a local script (`scripts/n8n-sync/deploy-n8n-baseline.cjs`) against `globus-cluster-qa` during apply, driven by the manifests in `environments/qa/n8n-sync`. The script is non-destructive: it never updates or deletes existing workflows, it only imports manifest workflows that are absent from the n8n database, keeping their source-controlled IDs. `workflow-manifest.json` pins an exact 40-character `sourceCommit` of `Alliance-Strategies/n8n-workflows` (the apply fails if the pin is missing) and carries `workflowIdAliases`, `credentialIdAliases` and `nodeCredentialBindings` used to migrate stale workflow IDs and credential references. Before any write the script checks that the kubectl context matches the cluster, that every pinned file resolves, that names and IDs do not collide with existing workflows, and that every referenced credential and sub-workflow exists. After importing it publishes the workflows that are meant to be active, restarts the `n8n` deployment, and verifies that exactly one active `[Globus] Dispatcher: Event Router` owns `POST globus-events`. The earlier delete-first n8n sync was retired (PR #225), and the production n8n auto-apply workflow is no longer in the repo's workflow listing. The n8n Deployment env and the `n8n-kc-admin` k8s secret are set by hand (documented in the QA wipe checklist) and are lost on a rebuild unless reproduced.
- Secrets: Terraform creates Secret Manager containers only; values are uploaded out of band.
- Sandbox pool: `branching-sandbox.tf` defines three Cloud Run slots in `us-central1` with their own WIF pool. Its GitHub provider accepts only the sandbox repo, on one of two paths: `release/*` refs with `job_workflow_ref` pinned to that repo's `.github/workflows/deploy.yml` on a `refs/heads/release/` ref, or `hotfix/*` refs, which may deploy to a QA slot only through the dispatch-only `trusted-deploy-qa.yml` pinned at `main`. Domain mappings for them are created by a verified human, not Terraform.
- Host Portal pool: `host-portal-pool.tf` defines three server slots and three client slots (`host-portal-server-qa-N` and `host-portal-client-qa-N`) in `us-central1` for `release/**` branches of `Alliance-Strategies/HostPortalPrototype`. Terraform sets a bootstrap image only, and `env` is also in `ignore_changes`. The slots run as the existing `host-portal-qa-runtime` identity. Federation uses its own WIF pool `github-actions-hostportal`, limited to that repo and `release/*` refs, with `job_workflow_ref` pinned to the repo's `.github/workflows/deploy.yml` on a `refs/heads/release/` ref. `hotfix/*` refs are also accepted, but only through the dispatch-only `trusted-deploy-qa.yml` pinned at `main`. The dedicated deployer (`module.hostportal_deployer_qa`) is described as `release/*` only, with `run.developer` on the six pool services, Artifact Registry writer and build-secret reader. Domain mappings (`qa-N.hosts.allianceabroad.com` for each client slot, `qa-N-api.hosts.allianceabroad.com` for each server slot) are created by hand. The pool is net-new; the legacy QA pair and its deploy identity are untouched until Phase 5.
- Globus pool: `globus-pool.tf` defines three slots (`qa-N.globus.allianceabroad.com`) for `release/**` branches of `Alliance-Strategies/BackOfficePrototype` on `globus-cluster-qa`. Each slot gets its own database (`globus_qa_1` to `globus_qa_3`) on the existing `globus-db-qa` instance, next to the legacy `globus_staging`. The databases use `deletion_policy = "ABANDON"` so that Terraform never drops one while a branch is deployed to it. The slots run as the existing `globus-qa-runtime` identity, with one Workload Identity binding per slot Kubernetes service account (`globus-qa-N-ksa` in namespace `globus-qa-N`). Federation uses its own WIF pool `github-actions-globus`, limited to that repo and `release/**` refs, plus `hotfix/**` refs, which may deploy to a QA slot only through the dispatch-only reusable workflow `trusted-deploy-qa.yml` pinned at `main`. The dedicated deployer (`module.globus_deployer_qa`) is described as `deploy-release-qa.yml` on `release/**` only. It has kubectl via Connect Gateway on `globus-cluster-qa`, push access to the Artifact Registry repo `globus` and read access to the Vite build secrets. Ingress is one global address `globus-qa-gateway-ip`, claimed by the pool Gateway, plus a DNS-authorized certificate per host in the certificate map `globus-qa-pool-cert-map`. Terraform enables the Certificate Manager API (`certificatemanager.googleapis.com`) for the pool's DNS authorizations, certificates and maps. The API was first enabled by hand after the first QA apply of #366 failed with a "Certificate Manager API has not been used in project" error. Terraform also grants `roles/certificatemanager.editor` for the Globus QA pool, mirroring dev. The GoDaddy records (an ACME CNAME and an A record pointing at the Gateway IP for each host) are created by hand from the `globus_qa_pool_dns_records` output.
- PPP2 and WildPacks providers: the GitHub WIF providers in `ppp2-pool.tf` and `wildpacks.tf` accept only their own repository on `release/*` refs, with `job_workflow_ref` pinned to that repository's `.github/workflows/deploy.yml` on a `refs/heads/release/` ref, or on `hotfix/*` refs, which are accepted only through the dispatch-only `trusted-deploy-qa.yml` pinned at `main`.

_Terraform inventory generated from `alliance-gcp-infra` at commit `fc9bd1d`. Live snapshots: `quality-assurance-496022` read 2026-10-01. Snapshots hold names and a few stable attributes only, never secret values._

## Terraform-managed resources

Every module call and resource block under `environments/qa` (206 in total), grouped by category.

### Identity and access (service accounts, IAM bindings, Workload Identity)

70 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `google_artifact_registry_repository_iam_member.sandbox_deployer_writer` | `google_artifact_registry_repository_iam_member` | `roles/artifactregistry.writer -> serviceAccount:${module.sandbox_deployer.email}` | `branching-sandbox.tf:176` |
| `google_cloud_run_service_iam_member.host_portal_qa_client_invokers` | `google_cloud_run_service_iam_member` | `roles/run.invoker -> each.value` (one per `for_each` key) | `host-portal-qa-cloud-run.tf:293` |
| `google_cloud_run_service_iam_member.host_portal_qa_server_invokers` | `google_cloud_run_service_iam_member` | `roles/run.invoker -> each.value` (one per `for_each` key) | `host-portal-qa-cloud-run.tf:170` |
| `google_cloud_run_service_iam_member.ppp2_client_run_qa_invokers` | `google_cloud_run_service_iam_member` | `roles/run.invoker -> each.value` (one per `for_each` key) | `main.tf:1620` |
| `google_cloud_run_service_iam_member.ppp2_server_run_qa_invokers` | `google_cloud_run_service_iam_member` | `roles/run.invoker -> each.value` (one per `for_each` key) | `main.tf:1791` |
| `google_cloud_run_service_iam_member.wildpacks_qa_invokers` | `google_cloud_run_service_iam_member` | `roles/run.invoker -> each.value` (one per `for_each` key) | `main.tf:1502` |
| `google_cloud_run_v2_service_iam_member.sandbox_deployer` | `google_cloud_run_v2_service_iam_member` | `google_cloud_run_v2_service.sandbox_web[each.key].name` (one per `for_each` key) | `branching-sandbox.tf:153` |
| `google_cloud_run_v2_service_iam_member.sandbox_public` | `google_cloud_run_v2_service_iam_member` | `google_cloud_run_v2_service.sandbox_web[each.key].name` (one per `for_each` key) | `branching-sandbox.tf:143` |
| `google_iam_workload_identity_pool_provider.sandbox` | `google_iam_workload_identity_pool_provider` | `GitHub` | `branching-sandbox.tf:67` |
| `google_iam_workload_identity_pool.sandbox` | `google_iam_workload_identity_pool` | `GitHub Actions (sandbox)` | `branching-sandbox.tf:60` |
| `google_project_iam_member.cloudbuild_default_sa_ar_writer` | `google_project_iam_member` | `roles/artifactregistry.writer -> serviceAccount:735345320299-compute@developer.gserviceaccount.com` | `main.tf:2089` |
| `google_project_iam_member.cloudbuild_default_sa_log_writer` | `google_project_iam_member` | `roles/logging.logWriter -> serviceAccount:735345320299-compute@developer.gserviceaccount.com` | `main.tf:2083` |
| `google_project_iam_member.cloudbuild_default_sa_storage_viewer` | `google_project_iam_member` | `roles/storage.objectViewer -> serviceAccount:735345320299-compute@developer.gserviceaccount.com` | `main.tf:2077` |
| `google_project_iam_member.env_audit_agent_sa_cloudsql_viewer` | `google_project_iam_member` | `roles/cloudsql.viewer -> serviceAccount:env-audit-agent-sa@alliance-platform-agents.iam.gserviceaccount.com` | `env-audit-agent-sa-viewer-roles.tf:19` |
| `google_project_iam_member.env_audit_agent_sa_compute_viewer` | `google_project_iam_member` | `roles/compute.viewer -> serviceAccount:env-audit-agent-sa@alliance-platform-agents.iam.gserviceaccount.com` | `env-audit-agent-sa-viewer-roles.tf:25` |
| `google_project_iam_member.env_audit_agent_sa_secretmanager_viewer` | `google_project_iam_member` | `roles/secretmanager.viewer -> serviceAccount:env-audit-agent-sa@alliance-platform-agents.iam.gserviceaccount.com` | `env-audit-agent-sa-viewer-roles.tf:13` |
| `google_project_iam_member.environment_index_platform_sa_run_viewer` | `google_project_iam_member` | `roles/run.viewer` | `main.tf` |
| `google_project_iam_member.environment_index_sa_run_viewer` | `google_project_iam_member` | `roles/run.viewer` | `main.tf` |
| `google_project_iam_member.gke_workload_health_agent_sa_cross_project` | `google_project_iam_member` | `each.value -> serviceAccount:gke-workload-health-agent-sa@skilled-script-469819-f2.iam.gserviceaccount.com` (one per `for_each` key) | `main.tf:2294` |
| `google_project_iam_member.host_portal_qa_runtime_run_developer` | `google_project_iam_member` | `roles/run.developer -> serviceAccount:${module.host_portal_qa_runtime.email}` | `host-portal-qa-cloud-run.tf:310` |
| `google_project_iam_member.host_portal_qa_runtime_sa_user` | `google_project_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:${module.host_portal_qa_runtime.email}` | `host-portal-qa-cloud-run.tf:316` |
| `google_project_iam_member.manus_agent_sa_artifactregistry_writer` | `google_project_iam_member` | `roles/artifactregistry.writer -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-db-query-proxy-build-deploy.tf:16` |
| `google_project_iam_member.manus_agent_sa_cloudbuild_editor` | `google_project_iam_member` | `roles/cloudbuild.builds.editor -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-db-query-proxy-build-deploy.tf:10` |
| `google_project_iam_member.manus_agent_sa_cloudsql_admin` | `google_project_iam_member` | `roles/cloudsql.admin -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-cloudsql-admin.tf:13` |
| `google_project_iam_member.manus_agent_sa_logging_viewer` | `google_project_iam_member` | `roles/logging.viewer -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-logging-viewer.tf:10` |
| `google_project_iam_member.manus_agent_sa_run_admin` | `google_project_iam_member` | `roles/run.admin -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-db-query-proxy-build-deploy.tf:22` |
| `google_project_iam_member.manus_agent_sa_secretmanager_admin` | `google_project_iam_member` | `roles/secretmanager.admin -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-secretmanager-admin.tf:12` |
| `google_project_iam_member.manus_agent_sa_security_reviewer` | `google_project_iam_member` | `roles/iam.securityReviewer -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-security-reviewer.tf:13` |
| `google_project_iam_member.manus_agent_sa_service_account_user` | `google_project_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-db-query-proxy-build-deploy.tf:28` |
| `google_project_iam_member.n8n_runtime_dev_compute_viewer` | `google_project_iam_member` | `roles/compute.viewer -> serviceAccount:n8n-runtime-dev@skilled-script-469819-f2.iam.gserviceaccount.com` | `cert-monitor-n8n-runtime-sa-compute-viewer.tf:12` |
| `google_project_iam_member.platform_hub_sa_cross_project` | `google_project_iam_member` | `each.value -> serviceAccount:platform-hub-sa@alliance-mcp-services.iam.gserviceaccount.com` (one per `for_each` key) | `main.tf:2279` |
| `google_project_iam_member.sandbox_deployer_run_viewer` | `google_project_iam_member` | `roles/run.viewer -> serviceAccount:${module.sandbox_deployer.email}` | `branching-sandbox.tf:164` |
| `google_project_iam_member.security_agent_sa_cloudkms_viewer` | `google_project_iam_member` | `roles/cloudkms.viewer -> serviceAccount:security-agent-sa@alliance-platform-agents.iam.gserviceaccount.com` | `security-agent-sa-cloudkms-viewer.tf:13` |
| `google_project_iam_member.security_agent_sa_cloudsql_viewer` | `google_project_iam_member` | `roles/cloudsql.viewer -> serviceAccount:security-agent-sa@alliance-platform-agents.iam.gserviceaccount.com` | `security-agent-sa-cloudsql-viewer.tf:14` |
| `google_project_iam_member.user_addition_agent_sa_iam_viewer` | `google_project_iam_member` | `roles/iam.securityReviewer -> serviceAccount:user-addition-agent-sa@skilled-script-469819-f2.iam.gserviceaccount.com` | `user-addition-agent-sa-iam-viewer.tf:8` |
| `google_service_account_iam_member.external_secrets_qa_runtime_wi` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> serviceAccount:${var.project_id}.svc.id.goog[external-secrets/external-secrets]` | `main.tf:2122` |
| `google_service_account_iam_member.globus_qa_runtime_self_sign` | `google_service_account_iam_member` | `roles/iam.serviceAccountTokenCreator -> serviceAccount:${module.globus_qa_runtime.email}` | `main.tf:475` |
| `google_service_account_iam_member.globus_qa_runtime_wi` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> serviceAccount:${var.project_id}.svc.id.goog[globus-qa/globus-qa-ksa]` | `main.tf:2103` |
| `google_service_account_iam_member.host_portal_qa_runtime_github_wif` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> principalSet://iam.googleapis.com/${module.wif_pool_github_actions.name}/attribute.repository/Alliance-Strategies/HostPortalPrototype` | `host-portal-qa-cloud-run.tf:326` |
| `google_service_account_iam_member.manus_agent_sa_karin_caller_token_creator` | `google_service_account_iam_member` | `roles/iam.serviceAccountTokenCreator -> serviceAccount:manus-agent-sa@${var.project_id}.iam.gserviceaccount.com` | `manus-agent-sa-db-query-proxy-build-deploy.tf:40` |
| `google_service_account_iam_member.n8n_runtime_qa_wi` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> serviceAccount:${var.project_id}.svc.id.goog[n8n/n8n-ksa]` | `main.tf:2116` |
| `google_service_account_iam_member.platform_hub_sa_tc_manus` | `google_service_account_iam_member` | `roles/iam.serviceAccountTokenCreator -> serviceAccount:platform-hub-sa@alliance-mcp-services.iam.gserviceaccount.com` | `main.tf:2312` |
| `google_service_account_iam_member.ppp2_deployer_can_actas_client` | `google_service_account_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:${module.ppp2_deployer.email}` | `main.tf:527` |
| `google_service_account_iam_member.ppp2_deployer_can_actas_keycloak_runtime_qa` | `google_service_account_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:${module.ppp2_deployer.email}` | `main.tf:538` |
| `google_service_account_iam_member.ppp2_deployer_can_actas_server` | `google_service_account_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:${module.ppp2_deployer.email}` | `main.tf:521` |
| `google_service_account_iam_member.sandbox_deployer_actas` | `google_service_account_iam_member` | `roles/iam.serviceAccountUser -> serviceAccount:${module.sandbox_deployer.email}` | `branching-sandbox.tf:170` |
| `google_service_account_iam_member.sandbox_deployer_wif` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.sandbox.name}/attribute.repository/${local.sandbox_repo}` | `branching-sandbox.tf:86` |
| `google_service_account_iam_member.terraform_ci_viewer_qa_wif` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> principalSet://iam.googleapis.com/${module.wif_pool_github_actions_iac.name}/attribute.repository/Alliance-Strategies/alliance-gcp-infra` | `main.tf:288` |
| `google_service_account_iam_member.terraform_tf_apply_qa_wif` | `google_service_account_iam_member` | `roles/iam.workloadIdentityUser -> principalSet://iam.googleapis.com/${module.wif_pool_github_actions_apply_qa.name}/attribute.repository/Alliance-Strategies/alliance-gcp-infra` | `ci-terraform-apply.tf:45` |
| `google_storage_bucket_iam_member.globus_qa_runtime_bucket_reader_docs` | `google_storage_bucket_iam_member` | `roles/storage.legacyBucketReader -> serviceAccount:${module.globus_qa_runtime.email}` | `main.tf:459` |
| `google_storage_bucket_iam_member.globus_qa_runtime_bucket_reader_photos` | `google_storage_bucket_iam_member` | `roles/storage.legacyBucketReader -> serviceAccount:${module.globus_qa_runtime.email}` | `main.tf:465` |
| `google_storage_bucket_iam_member.terraform_ci_viewer_qa_tfstate_lock` | `google_storage_bucket_iam_member` | `roles/storage.objectUser -> serviceAccount:${module.terraform_ci_viewer_qa.email}` | `main.tf:300` |
| `module.cloudbuild_qa` | `service-account` | `cloudbuild-qa` | `main.tf:414` |
| `module.cloudbuild_teams_notifier_sa` | `service-account` | `cloudbuild-teams-notifier` | `main.tf:399` |
| `module.external_secrets_qa_runtime` | `service-account` | `external-secrets-qa-runtime` | `main.tf:355` |
| `module.github_actions_mcp_qa` | `service-account` | `github-actions-mcp-qa` | `main.tf:576` |
| `module.globus_qa_runtime` | `service-account` | `globus-qa-runtime` | `main.tf:434` |
| `module.host_portal_qa_runtime` | `service-account` | `host-portal-qa-runtime` | `main.tf:376` |
| `module.keycloak_runtime_qa` | `service-account` | `keycloak-runtime-qa` | `main.tf:563` |
| `module.mishkin_db_export_import` | `service-account` | `mishkin-db-export-import` | `drift-reconciliation-2026-09-03.tf:25` |
| `module.n8n_runtime_qa` | `service-account` | `n8n-runtime-qa` | `main.tf:364` |
| `module.ppp2_client_qa` | `service-account` | `ppp2-client-qa` | `main.tf:554` |
| `module.ppp2_deployer` | `service-account` | `ppp2-deployer` | `main.tf:481` |
| `module.ppp2_server_qa` | `service-account` | `ppp2-server-qa` | `main.tf:544` |
| `module.sandbox_deployer` | `service-account` | `sandbox-deployer-qa` | `branching-sandbox.tf:51` |
| `module.sandbox_runtime` | `service-account` | `sandbox-runtime-qa` | `branching-sandbox.tf:42` |
| `module.terraform_ci_viewer_qa` | `service-account` | `terraform-ci-viewer-qa` | `main.tf:275` |
| `module.terraform_tf_qa` | `service-account` | `terraform-tf` | `main.tf:620` |
| `module.wif_pool_github_actions` | `workload-identity-pool` | `github-actions-pool` | `main.tf:306` |
| `module.wif_pool_github_actions_apply_qa` | `workload-identity-pool` | `github-actions-apply-qa` | `ci-terraform-apply.tf:22` |
| `module.wif_pool_github_actions_iac` | `workload-identity-pool` | `github-actions-iac` | `main.tf:228` |
| `module.wildpacks_qa_runtime` | `service-account` | `wildpacks-qa-runtime` | `main.tf:390` |

### Secrets (Secret Manager containers)

93 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `google_secret_manager_secret.ppp2_globus_api_key_noenv` | `google_secret_manager_secret` | `ppp2-globus-api-key` | `main.tf:507` |
| `google_secret_manager_secret.ppp2_keycloak_admin_client_secret_noenv` | `google_secret_manager_secret` | `ppp2-keycloak-admin-client-secret` | `main.tf:499` |
| `module.cloudbuild_teams_notifier_qa_github_token` | `secret-manager-secret` | `cloudbuild-teams-notifier-qa-github-token` | `main.tf:1174` |
| `module.cloudbuild_teams_notifier_qa_webhook_url` | `secret-manager-secret` | `cloudbuild-teams-notifier-qa-webhook-url` | `main.tf:1182` |
| `module.github_qa_oauth_token` | `secret-manager-secret` | `github-qa-oauth-token` | `main.tf:2010` |
| `module.globus_google_maps_api_key` | `secret-manager-secret` | `globus-google-maps-api-key` | `drift-reconciliation-2026-09-03.tf:47` |
| `module.globus_posthog_host` | `secret-manager-secret` | `globus-posthog-host` | `drift-reconciliation-2026-09-03.tf:61` |
| `module.globus_qa_docai_processor_id` | `secret-manager-secret` | `globus-qa-docai-processor-id` | `main.tf:807` |
| `module.globus_qa_environment` | `secret-manager-secret` | `globus-qa-environment` | `main.tf:828` |
| `module.globus_qa_hubspot_api_key` | `secret-manager-secret` | `globus-qa-hubspot-api-key` | `main.tf:838` |
| `module.globus_qa_mailjet_api_key` | `secret-manager-secret` | `globus-qa-mailjet-api-key` | `main.tf:858` |
| `module.globus_qa_mailjet_from_email` | `secret-manager-secret` | `globus-qa-mailjet-from-email` | `main.tf:873` |
| `module.globus_qa_n8n_api_key` | `secret-manager-secret` | `globus-qa-n8n-api-key` | `main.tf:850` |
| `module.globus_qa_n8n_globus_api_key` | `secret-manager-secret` | `globus-qa-n8n-globus-api-key` | `main.tf:883` |
| `module.globus_qa_netsuite_access_token` | `secret-manager-secret` | `globus-qa-netsuite-access-token` | `main.tf:929` |
| `module.globus_qa_netsuite_account_id` | `secret-manager-secret` | `globus-qa-netsuite-account-id` | `main.tf:897` |
| `module.globus_qa_netsuite_consumer_key` | `secret-manager-secret` | `globus-qa-netsuite-consumer-key` | `main.tf:913` |
| `module.globus_qa_netsuite_consumer_secret` | `secret-manager-secret` | `globus-qa-netsuite-consumer-secret` | `main.tf:921` |
| `module.globus_qa_netsuite_realm` | `secret-manager-secret` | `globus-qa-netsuite-realm` | `main.tf:905` |
| `module.globus_qa_netsuite_token_secret` | `secret-manager-secret` | `globus-qa-netsuite-token-secret` | `main.tf:937` |
| `module.globus_qa_openai_api_key` | `secret-manager-secret` | `globus-qa-openai-api-key` | `main.tf:951` |
| `module.globus_qa_posthog_host` | `secret-manager-secret` | `globus-qa-posthog-host` | `main.tf:963` |
| `module.globus_qa_sandbox_mode` | `secret-manager-secret` | `globus-qa-sandbox-mode` | `main.tf:973` |
| `module.globus_qa_sendgrid_api_key` | `secret-manager-secret` | `globus-qa-sendgrid-api-key` | `main.tf:996` |
| `module.globus_qa_translation_endpoint` | `secret-manager-secret` | `globus-qa-translation-endpoint` | `drift-reconciliation-2026-09-03.tf:73` |
| `module.globus_qa_translation_glossary_id` | `secret-manager-secret` | `globus-qa-translation-glossary-id` | `drift-reconciliation-2026-09-03.tf:85` |
| `module.globus_qa_vertex_qa_agent_id` | `secret-manager-secret` | `globus-qa-vertex-qa-agent-id` | `main.tf:1019` |
| `module.globus_short_admin_db_url` | `secret-manager-secret` | `globus-admin-db-url` | `globus-short-secret-names-pilot.tf:344` |
| `module.globus_short_applicant_portal_posthog_key` | `secret-manager-secret` | `globus-applicant-portal-posthog-key` | `globus-short-secret-names-pilot.tf:288` |
| `module.globus_short_applicant_portal_webhook_url` | `secret-manager-secret` | `globus-applicant-portal-webhook-url` | `globus-short-secret-names-pilot.tf:295` |
| `module.globus_short_asana_access_token` | `secret-manager-secret` | `globus-asana-access-token` | `globus-short-secret-names-pilot.tf:84` |
| `module.globus_short_asana_client_id` | `secret-manager-secret` | `globus-asana-client-id` | `globus-short-secret-names-pilot.tf:70` |
| `module.globus_short_asana_client_secret` | `secret-manager-secret` | `globus-asana-client-secret` | `globus-short-secret-names-pilot.tf:77` |
| `module.globus_short_backoffice_api_key` | `secret-manager-secret` | `globus-backoffice-api-key` | `globus-short-secret-names-pilot.tf:98` |
| `module.globus_short_brave_api_key` | `secret-manager-secret` | `globus-brave-api-key` | `globus-short-secret-names-pilot.tf:176` |
| `module.globus_short_database_url` | `secret-manager-secret` | `globus-database-url` | `globus-short-secret-names-pilot.tf:28` |
| `module.globus_short_document_bucket` | `secret-manager-secret` | `globus-document-bucket` | `globus-short-secret-names-pilot.tf:190` |
| `module.globus_short_entra_client_id` | `secret-manager-secret` | `globus-entra-client-id` | `globus-short-secret-names-pilot.tf:211` |
| `module.globus_short_entra_client_secret` | `secret-manager-secret` | `globus-entra-client-secret` | `globus-short-secret-names-pilot.tf:218` |
| `module.globus_short_entra_tenant_id` | `secret-manager-secret` | `globus-entra-tenant-id` | `globus-short-secret-names-pilot.tf:204` |
| `module.globus_short_github_config_repo_token` | `secret-manager-secret` | `globus-github-config-repo-token` | `globus-short-secret-names-pilot.tf:323` |
| `module.globus_short_github_config_repo_write_token` | `secret-manager-secret` | `globus-github-config-repo-write-token` | `globus-short-secret-names-pilot.tf:330` |
| `module.globus_short_host_portal_webhook_url` | `secret-manager-secret` | `globus-host-portal-webhook-url` | `globus-short-secret-names-pilot.tf:302` |
| `module.globus_short_internal_ocr_api_key` | `secret-manager-secret` | `globus-internal-ocr-api-key` | `globus-short-secret-names-pilot.tf:309` |
| `module.globus_short_internal_translation_api_key` | `secret-manager-secret` | `globus-internal-translation-api-key` | `globus-short-secret-names-pilot.tf:316` |
| `module.globus_short_mcp_api_key` | `secret-manager-secret` | `globus-mcp-api-key` | `globus-short-secret-names-pilot.tf:197` |
| `module.globus_short_n8n_api_key` | `secret-manager-secret` | `globus-n8n-api-key` | `globus-short-secret-names-pilot.tf:253` |
| `module.globus_short_n8n_api_url` | `secret-manager-secret` | `globus-n8n-api-url` | `globus-short-secret-names-pilot.tf:246` |
| `module.globus_short_n8n_job_order_webhook_url` | `secret-manager-secret` | `globus-n8n-job-order-webhook-url` | `globus-short-secret-names-pilot.tf:260` |
| `module.globus_short_n8n_webhook_url_application` | `secret-manager-secret` | `globus-n8n-webhook-url-application` | `globus-short-secret-names-pilot.tf:267` |
| `module.globus_short_n8n_webhook_url_document` | `secret-manager-secret` | `globus-n8n-webhook-url-document` | `globus-short-secret-names-pilot.tf:274` |
| `module.globus_short_oidc_client_id` | `secret-manager-secret` | `globus-oidc-client-id` | `globus-short-secret-names-pilot.tf:232` |
| `module.globus_short_oidc_client_secret` | `secret-manager-secret` | `globus-oidc-client-secret` | `globus-short-secret-names-pilot.tf:239` |
| `module.globus_short_oidc_discovery_url` | `secret-manager-secret` | `globus-oidc-discovery-url` | `globus-short-secret-names-pilot.tf:225` |
| `module.globus_short_openai_api_key` | `secret-manager-secret` | `globus-openai-api-key` | `globus-short-secret-names-pilot.tf:91` |
| `module.globus_short_partner_pipeline_api_key` | `secret-manager-secret` | `globus-partner-pipeline-api-key` | `globus-short-secret-names-pilot.tf:56` |
| `module.globus_short_partner_pipeline_api_url` | `secret-manager-secret` | `globus-partner-pipeline-api-url` | `globus-short-secret-names-pilot.tf:49` |
| `module.globus_short_partner_pipeline_user_id` | `secret-manager-secret` | `globus-partner-pipeline-user-id` | `globus-short-secret-names-pilot.tf:63` |
| `module.globus_short_photo_bucket` | `secret-manager-secret` | `globus-photo-bucket` | `globus-short-secret-names-pilot.tf:183` |
| `module.globus_short_pii_encryption_key` | `secret-manager-secret` | `globus-pii-encryption-key` | `globus-short-secret-names-pilot.tf:105` |
| `module.globus_short_portal_session_secret` | `secret-manager-secret` | `globus-portal-session-secret` | `globus-short-secret-names-pilot.tf:42` |
| `module.globus_short_posthog_key` | `secret-manager-secret` | `globus-posthog-key` | `globus-short-secret-names-pilot.tf:281` |
| `module.globus_short_seed_admin_email` | `secret-manager-secret` | `globus-seed-admin-email` | `globus-short-secret-names-pilot.tf:155` |
| `module.globus_short_seed_admin_password` | `secret-manager-secret` | `globus-seed-admin-password` | `globus-short-secret-names-pilot.tf:162` |
| `module.globus_short_sendgrid_api_key` | `secret-manager-secret` | `globus-sendgrid-api-key` | `globus-short-secret-names-pilot.tf:126` |
| `module.globus_short_sendgrid_from_email` | `secret-manager-secret` | `globus-sendgrid-from-email` | `globus-short-secret-names-pilot.tf:133` |
| `module.globus_short_sendgrid_from_name` | `secret-manager-secret` | `globus-sendgrid-from-name` | `globus-short-secret-names-pilot.tf:148` |
| `module.globus_short_session_secret` | `secret-manager-secret` | `globus-session-secret` | `globus-short-secret-names-pilot.tf:35` |
| `module.globus_short_sevis_cert_encryption_key` | `secret-manager-secret` | `globus-sevis-cert-encryption-key` | `globus-short-secret-names-pilot.tf:119` |
| `module.globus_short_sevis_cert_file` | `secret-manager-secret` | `globus-sevis-cert-file` | `globus-short-secret-names-pilot.tf:337` |
| `module.globus_short_sevis_cert_password` | `secret-manager-secret` | `globus-sevis-cert-password` | `globus-short-secret-names-pilot.tf:112` |
| `module.globus_short_vite_google_maps_api_key` | `secret-manager-secret` | `globus-vite-google-maps-api-key` | `globus-short-secret-names-pilot.tf:169` |
| `module.host_portal_qa_anthropic_api_key` | `secret-manager-secret` | `host-portal-qa-anthropic-api-key` | `main.tf:1030` |
| `module.host_portal_qa_auth0_audience` | `secret-manager-secret` | `host-portal-qa-auth0-audience` | `main.tf:1038` |
| `module.host_portal_qa_auth0_client_id` | `secret-manager-secret` | `host-portal-qa-auth0-client-id` | `main.tf:1046` |
| `module.host_portal_qa_auth0_domain` | `secret-manager-secret` | `host-portal-qa-auth0-domain` | `main.tf:1054` |
| `module.host_portal_qa_auth0_mgmt_audience` | `secret-manager-secret` | `host-portal-qa-auth0-mgmt-audience` | `main.tf:1062` |
| `module.host_portal_qa_auth0_mgmt_client_id` | `secret-manager-secret` | `host-portal-qa-auth0-mgmt-client-id` | `main.tf:1070` |
| `module.host_portal_qa_auth0_mgmt_client_secret` | `secret-manager-secret` | `host-portal-qa-auth0-mgmt-client-secret` | `main.tf:1078` |
| `module.host_portal_qa_auth0_namespace` | `secret-manager-secret` | `host-portal-qa-auth0-namespace` | `main.tf:1086` |
| `module.host_portal_qa_globus_apikey` | `secret-manager-secret` | `host-portal-qa-globus-apikey` | `main.tf:1094` |
| `module.host_portal_qa_globus_webhook_secret` | `secret-manager-secret` | `host-portal-qa-globus-webhook-secret` | `main.tf:1102` |
| `module.host_portal_qa_openai_api_key` | `secret-manager-secret` | `host-portal-qa-openai-api-key` | `main.tf:1110` |
| `module.host_portal_qa_posthog_key` | `secret-manager-secret` | `host-portal-qa-posthog-key` | `main.tf:1118` |
| `module.host_portal_qa_sendgrid_api_key` | `secret-manager-secret` | `host-portal-qa-sendgrid-api-key` | `main.tf:1126` |
| `module.host_portal_qa_session_secret` | `secret-manager-secret` | `host-portal-qa-session-secret` | `main.tf:1134` |
| `module.keycloak_qa_admin_password` | `secret-manager-secret` | `keycloak-qa-admin-password` | `main.tf:1192` |
| `module.keycloak_qa_db_password` | `secret-manager-secret` | `keycloak-qa-db-password` | `main.tf:1332` |
| `module.n8n_qa_db_password` | `secret-manager-secret` | `n8n-qa-db-password` | `main.tf:1345` |
| `module.n8n_qa_encryption_key` | `secret-manager-secret` | `n8n-qa-encryption-key` | `main.tf:1353` |
| `module.ppp2_qa_globus_api_key` | `secret-manager-secret` | `ppp2-qa-globus-api-key` | `main.tf:1143` |
| `module.ppp2_qa_keycloak_admin_client_secret` | `secret-manager-secret` | `ppp2-qa-keycloak-admin-client-secret` | `main.tf:1151` |
| `module.wildpacks_qa_hubspot_token` | `secret-manager-secret` | `wildpacks-qa-hubspot-token` | `main.tf:1161` |

### Compute (Cloud Run, GKE, jobs, schedulers)

10 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `google_cloud_run_service.host_portal_qa_client` | `google_cloud_run_service` | `host-portal-qa-client` | `host-portal-qa-cloud-run.tf:179` |
| `google_cloud_run_service.host_portal_qa_server` | `google_cloud_run_service` | `host-portal-qa-server` | `host-portal-qa-cloud-run.tf:17` |
| `google_cloud_run_service.ppp2_client_run_qa` | `google_cloud_run_service` | `ppp2-participant-client-qa` | `main.tf:1542` |
| `google_cloud_run_service.ppp2_server_run_qa` | `google_cloud_run_service` | `ppp2-participant-server-qa` | `main.tf:1634` |
| `google_cloud_run_service.wildpacks_qa` | `google_cloud_run_service` | `wildpacks-qa` | `main.tf:1412` |
| `google_cloud_run_v2_service.sandbox_web` | `google_cloud_run_v2_service` | `sandbox-web-${local.sandbox_env}-${each.key}` (one per `for_each` key) | `branching-sandbox.tf:92` |
| `module.cloudbuild_teams_notifier` | `cloud-run-service` | `cloudbuild-teams-notifier` | `main.tf:1516` |
| `module.globus_cluster_qa` | `gke-autopilot-cluster` | `globus-cluster-qa` | `main.tf:1289` |
| `module.keycloak_qa` | `cloud-run-service` | `applicant-keycloak` | `main.tf:1822` |
| `null_resource.n8n_baseline_deploy_qa` | `null_resource` |  | `main.tf:1366` |

### Data stores (Cloud SQL, Redis, Firestore, Cloud Storage)

8 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `module.bucket_globus_qa_documents` | `storage-bucket` | `globus-qa-documents` | `main.tf:2165` |
| `module.bucket_globus_qa_photos` | `storage-bucket` | `globus-qa-photos` | `main.tf:2182` |
| `module.bucket_qa_globus_branding_assets` | `storage-bucket` | `qa-globus-branding-assets` | `main.tf:2244` |
| `module.bucket_qa_tfstate_own_backend` | `storage-bucket` | `qa-tfstate-quality-assurance-496022` | `main.tf:2251` |
| `module.globus_db_qa` | `cloud-sql-postgres` | `globus-db-qa` | `main.tf:695` |
| `module.keycloak_db_qa` | `cloud-sql-postgres` | `keycloak-db-qa` | `main.tf:736` |
| `module.n8n_db_qa` | `cloud-sql-postgres` | `n8n-db-qa` | `main.tf:723` |
| `module.n8n_redis_qa` | `redis-instance` | `n8n-redis-qa` | `main.tf:1305` |

### Networking and load balancing

9 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `google_compute_global_address.globus_qa_ip` | `google_compute_global_address` | `globus-qa-ip` | `main.tf:1930` |
| `google_compute_global_address.host_portal_qa_ip` | `google_compute_global_address` | `host-portal-qa-ip` | `main.tf:1935` |
| `google_compute_global_address.n8n_qa_ip` | `google_compute_global_address` | `n8n-qa-ip` | `main.tf:1944` |
| `module.fw_default_allow_internal_qa` | `firewall-rule` | `default-allow-internal` | `main.tf:195` |
| `module.fw_qa_allow_internal` | `firewall-rule` | `fw-qa-allow-internal` | `main.tf:210` |
| `module.network_default_qa` | `network` | `default` | `main.tf:89` |
| `module.network_skilled_script_vpc_qa` | `network` | `skilled-script-vpc-qa` | `main.tf:146` |
| `module.ppp2_applicants_qa_lb` | `load-balancer-https` |  | `main.tf:1949` |
| `module.qa_apply_domain_mapping` | `domain-mapping` | `qa.apply.allianceabroad.com` | `main.tf:1982` |

### Build, registry and delivery

9 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `module.ar_applicants_portal_qa` | `artifact-registry` | `applicants-portal` | `main.tf:1251` |
| `module.ar_globus_qa` | `artifact-registry` | `globus` | `main.tf:1212` |
| `module.ar_keycloak_upstream_qa` | `artifact-registry` | `keycloak-upstream` | `main.tf:1263` |
| `module.ar_portal_qa` | `artifact-registry` | `portal` | `main.tf:1220` |
| `module.ar_ppp2_qa` | `artifact-registry` | `ppp2` | `main.tf:1236` |
| `module.ar_wildpacks_qa` | `artifact-registry` | `wildpacks` | `main.tf:1228` |
| `module.cloudbuild_github_connection_qa` | `cloudbuild-github-connection` |  | `main.tf:2030` |
| `module.sandbox_artifact_registry` | `artifact-registry` | `sandbox` | `branching-sandbox.tf:34` |
| `module.trigger_wildpacks_qa` | `cloudbuild-trigger` | `wildpacks-qa` | `main.tf:2134` |

### Project services, messaging and other

7 items.

| Terraform address | Module or resource type | Identifier | Defined in |
| --- | --- | --- | --- |
| `data.google_project.qa` | `google_project` |  | `host-portal-qa-cloud-run.tf:302` |
| `google_apikeys_key.globus_qa_maps` | `google_apikeys_key` | `qa-globus-maps` | `main.tf:2206` |
| `google_logging_metric.password_reset_mail_error_qa` | `google_logging_metric` | `password-reset-mail-error-qa` | `mail-deliverability-alerts.tf:27` |
| `google_logging_metric.realm_smtp_missing_qa` | `google_logging_metric` | `realm-smtp-missing-qa` | `mail-deliverability-alerts.tf:74` |
| `google_monitoring_alert_policy.password_reset_mail_error_qa` | `google_monitoring_alert_policy` | `QA: password reset mail failing (password_reset_mail outcome=error)` | `mail-deliverability-alerts.tf:45` |
| `google_monitoring_alert_policy.realm_smtp_missing_qa` | `google_monitoring_alert_policy` | `QA: Keycloak realm SMTP relay missing (realm_smtp configured=false)` | `mail-deliverability-alerts.tf:92` |
| `module.project_services_qa` | `project-services` | `53 items: analyticshub, artifactregistry, autoscaling, ...` | `main.tf:27` |

## Live state

### Terraform compared with live state

| Resource type | Live | In Terraform | Matched | Live only (unmanaged) | Platform-created | Terraform only | Name not resolvable |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `api` | 59 | 53 | 53 | 6 | 0 | 0 | 0 |
| `cloud_run_service` | 11 | 8 | 10 | 1 | 0 | 0 | 0 |
| `cloud_run_job` | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| `cloud_sql` | 3 | 3 | 3 | 0 | 0 | 0 | 0 |
| `redis` | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| `firestore` | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| `bucket` | 8 | 4 | 4 | 2 | 2 | 0 | 0 |
| `secret` | 97 | 93 | 93 | 4 | 0 | 0 | 0 |
| `service_account` | 21 | 17 | 17 | 3 | 1 | 0 | 0 |
| `wif_pool` | 4 | 4 | 3 | 1 | 0 | 0 | 1 |
| `artifact_registry` | 8 | 7 | 7 | 1 | 0 | 0 | 0 |
| `cloudbuild_trigger` | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| `pubsub_topic` | 5 | 0 | 0 | 1 | 4 | 0 | 0 |
| `scheduler_job` | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| `gke_cluster` | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| `domain_mapping` | 5 | 1 | 1 | 4 | 0 | 0 | 0 |
| `firewall_rule` | 7 | 2 | 2 | 0 | 5 | 0 | 0 |
| `vpc_network` | 2 | 2 | 2 | 0 | 0 | 0 | 0 |

### Live but not in Terraform

23 resources of the compared types exist in the project without a matching Terraform declaration.

| Resource type | Name |
| --- | --- |
| `api` | `apikeys` |
| `api` | `cloudasset` |
| `api` | `cloudkms` |
| `api` | `cloudscheduler` |
| `api` | `distance-matrix-backend` |
| `api` | `orgpolicy` |
| `cloud_run_service` | `db-query-proxy-qa` |
| `bucket` | `host-portal-db-backups-qa` |
| `bucket` | `host-portal-public-assets-qa` |
| `secret` | `db-query-proxy-qa-db-password` |
| `secret` | `db-query-proxy-qa-token` |
| `secret` | `globus-qa-template-admin-sendgrid-key` |
| `secret` | `n8n-qa-hubspot-webhook-secret` |
| `service_account` | `db-query-proxy-qa-runtime` |
| `service_account` | `karin-proxy-caller-qa` |
| `service_account` | `manus-agent-sa` |
| `wif_pool` | `github-actions-sandbox` |
| `artifact_registry` | `db-query-proxy` |
| `pubsub_topic` | `secret-rotation-notifications` |
| `domain_mapping` | `qa-1.sandbox.allianceabroad.com` |
| `domain_mapping` | `qa-2.sandbox.allianceabroad.com` |
| `domain_mapping` | `qa-3.sandbox.allianceabroad.com` |
| `domain_mapping` | `qa.apply.wildpacks.com` |

### In Terraform but not found live

_None._

### Created by GCP or GKE

These are created by the platform itself (default network rules, GKE firewall rules, default compute service accounts, Cloud Build buckets, container analysis topics) and are not drift.

| Resource type | Name |
| --- | --- |
| `bucket` | `qa-cloudbuild-staging-496022` |
| `bucket` | `quality-assurance-496022_cloudbuild` |
| `service_account` | `735345320299-compute` |
| `pubsub_topic` | `container-analysis-notes-v1` |
| `pubsub_topic` | `container-analysis-notes-v1beta1` |
| `pubsub_topic` | `container-analysis-occurrences-v1` |
| `pubsub_topic` | `container-analysis-occurrences-v1beta1` |
| `firewall_rule` | `gke-globus-cluster-qa-0b3cf4aa-all` |
| `firewall_rule` | `gke-globus-cluster-qa-0b3cf4aa-exkubelet` |
| `firewall_rule` | `gke-globus-cluster-qa-0b3cf4aa-inkubelet` |
| `firewall_rule` | `gke-globus-cluster-qa-0b3cf4aa-vms` |
| `firewall_rule` | `k8s-fw-l7--2188e7542740cb43` |

### Terraform items whose name is computed at plan time

These could not be matched by name, so they are not counted as matched or missing.

| Resource type | Terraform address | Defined in |
| --- | --- | --- |
| `wif_pool` | `google_iam_workload_identity_pool.sandbox` | `branching-sandbox.tf:60` |

### Resource types not compared

_None._

### Live reads that were denied or unavailable

| Read attempted | Result |
| --- | --- |
| billing info (gcloud billing projects describe) | cloudbilling.googleapis.com not enabled on project 735345320299; billing linked state unknown |
| Firestore databases list | firestore.googleapis.com not enabled on quality-assurance-496022 (treated as none; datastore.googleapis.com is enabled) |
| Certificate Manager certificates/maps | certificatemanager.googleapis.com not enabled (none) |
| Cloud Deploy pipelines | clouddeploy.googleapis.com not enabled (none) |
| IAP settings | iap.googleapis.com not enabled (none) |

### Collection notes

Caveats recorded when the live snapshot was read.

- Cloud Run jobs: none listed. Cloud Scheduler: none in us-central1 or northamerica-northeast1. Cloud Armor: none. Pub/Sub subscriptions: none.
- Cloud Build triggers: global region empty; the only trigger is wildpacks-qa in us-central1. Connection repos include ParticipantPortalPrototype (archived repo).
- Cloud SQL globus-db-qa contains a database named globus_staging (naming only).
- IAM contains a deleted SA member (db-query-proxy-qa-sa) on roles/cloudsql.client, and a temp external user [user account] with viewer/logging.viewer/cloudsql.instanceUser.
- mishkin-db-export-import SA holds roles/owner on the project.
- Cert mcrt-4bc55357 (qa.hosts.allianceabroad.com) on the GKE globus ingress proxy is PROVISIONING; host-portal-qa-cert (LB) is ACTIVE for the same domain.
- Host portal has both a Cloud Run + external-managed LB path (host-portal-qa-*) and a GKE ingress cert for qa.hosts.allianceabroad.com.
- Artifact Registry locations were not captured per repo (list output omitted location); inferred from image paths where marked.
- VPC connector regions were not captured; inferred from service usage.
- Subnet list: 46 total = 42 auto-mode default-network subnets + 4 custom on skilled-script-vpc-qa.
- Run via gcp_gcp_shell_exec (environment=qa); only read-only list/describe commands used. No secret values read.
- collected_at is date-only approximate (2026-10-01).

## Live inventory

Selected stable attributes read from the running project.

### Cloud Run services (live)

| Service | Region | Ingress | Scaling | Runs as |
| --- | --- | --- | --- | --- |
| `applicant-keycloak` | `northamerica-northeast1` | `all` | `1-3` | `keycloak-runtime-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `cloudbuild-teams-notifier` | `us-central1` | `internal-and-cloud-load-balancing` | `0-3` | `cloudbuild-teams-notifier@quality-assurance-496022.iam.gserviceaccount.com` |
| `db-query-proxy-qa` | `us-central1` | `all` | `0-2` | `db-query-proxy-qa-runtime@quality-assurance-496022.iam.gserviceaccount.com` |
| `host-portal-qa-client` | `us-central1` | `all` | `0-3` | `host-portal-qa-runtime@quality-assurance-496022.iam.gserviceaccount.com` |
| `host-portal-qa-server` | `us-central1` | `all` | `0-3` | `host-portal-qa-runtime@quality-assurance-496022.iam.gserviceaccount.com` |
| `ppp2-participant-client-qa` | `northamerica-northeast1` | `all` | `0-2` | `ppp2-client-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `ppp2-participant-server-qa` | `northamerica-northeast1` | `all` | `0-3` | `ppp2-server-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `sandbox-web-qa-1` | `us-central1` | `all` | `0-2` | `sandbox-runtime-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `sandbox-web-qa-2` | `us-central1` | `all` | `0-2` | `sandbox-runtime-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `sandbox-web-qa-3` | `us-central1` | `all` | `0-2` | `sandbox-runtime-qa@quality-assurance-496022.iam.gserviceaccount.com` |
| `wildpacks-qa` | `us-central1` | `all` | `0-3` | `wildpacks-qa-runtime@quality-assurance-496022.iam.gserviceaccount.com` |

### Cloud SQL instances (live)

| Instance | Region | Version | Tier | Edition | Private IP | Backups |
| --- | --- | --- | --- | --- | --- | --- |
| `keycloak-db-qa` | `us-central1` | `POSTGRES_15` | `db-g1-small` | `ENTERPRISE` | `true` | `true` |
| `globus-db-qa` | `us-central1` | `POSTGRES_16` | `db-custom-2-7680` | `ENTERPRISE` | `true` | `true` |
| `n8n-db-qa` | `us-central1` | `POSTGRES_15` | `db-g1-small` | `ENTERPRISE` | `true` | `true` |

### Redis instances (live)

| Instance | Region | Tier | Memory GB | Version |
| --- | --- | --- | --- | --- |
| `n8n-redis-qa` | `us-central1` | `BASIC` | `1` |  |

### Cloud Storage buckets (live)

| Bucket | Location | Class | Versioning | Public access prevention |
| --- | --- | --- | --- | --- |
| `globus-qa-documents` | `US-CENTRAL1` | `STANDARD` | `false` | `inherited` |
| `globus-qa-photos` | `US-CENTRAL1` | `STANDARD` | `false` | `inherited` |
| `host-portal-db-backups-qa` | `US-CENTRAL1` | `STANDARD` |  | `enforced` |
| `host-portal-public-assets-qa` | `US` | `STANDARD` |  | `inherited` |
| `qa-cloudbuild-staging-496022` | `NORTHAMERICA-NORTHEAST1` | `STANDARD` |  | `enforced` |
| `qa-globus-branding-assets` | `US-CENTRAL1` | `STANDARD` | `false` | `inherited` |
| `qa-tfstate-quality-assurance-496022` | `US-CENTRAL1` | `STANDARD` | `true` | `inherited` |
| `quality-assurance-496022_cloudbuild` | `US-CENTRAL1` | `STANDARD` |  | `enforced` |

### Workload Identity pools (live)

| Pool | State | Providers |
| --- | --- | --- |
| `github-actions-iac` | `ACTIVE` | `github-provider` |
| `github-actions-pool` | `ACTIVE` | `github-provider` |
| `github-actions-apply-qa` | `ACTIVE` | `github-provider` |
| `github-actions-sandbox` | `ACTIVE` | `github-provider` |
| `quality-assurance-496022.svc.id.goog` | `ACTIVE` |  |

## Reading the drift data

Counts and name lists are in the generated tables above and in [GCP Drift Report](/docs/gcp-drift-report). Patterns worth knowing:

- Most resource types match closely: Cloud SQL, Redis, GKE, VPC networks and the Cloud Build trigger are fully declared, and no declared resource is missing live (no IaC-only items).
- The live-only group is dominated by the `db-query-proxy` stack (Cloud Run service, runtime service account, Artifact Registry repo, two secrets), the `manus-agent-sa` identity (several `manus-agent-sa-*.tf` files grant it roles, but the account itself is not declared), the `karin-proxy-caller-qa` service account, two further secrets (`globus-qa-template-admin-sendgrid-key`, `n8n-qa-hubspot-webhook-secret`) and two host-portal buckets.
- `qa.apply.wildpacks.com` and three `qa-N.sandbox` domain mappings are live only. The sandbox mappings are created by hand by design (see `branching-sandbox.tf`).
- The `github-actions-sandbox` WIF pool, the `secret-rotation-notifications` topic and several enabled APIs (`apikeys`, `cloudasset`, `cloudkms`, `cloudscheduler`, `orgpolicy`, `distance-matrix-backend`) are not declared.
- Platform-created items (GKE firewall rules, Cloud Build buckets, Container Analysis topics) are expected and need no Terraform.
- Not covered: GKE in-cluster resources, IAM bindings as a drift type, and DNS records (the only DNS zone listed is the private GKE cluster zone).

## Security observations

These are facts from the live snapshot notes, to be reviewed, not conclusions:

- A temporary external-domain user account holds viewer, logging viewer and Cloud SQL instance user roles on the project.
- A personally named database export/import service account holds `roles/owner` (plus `roles/cloudsql.admin`) on the project. It was imported into Terraform by `drift-reconciliation-2026-09-03.tf`, so the grant is now tracked but still broad.
- The IAM policy still contains a deleted service account member (`db-query-proxy-qa-sa`) on `roles/cloudsql.client`.
- `db-query-proxy-qa` and the sandbox services have ingress `all` (as do most Cloud Run services here, all but `cloudbuild-teams-notifier`). Terraform grants `roles/run.invoker` to `allUsers` on the three sandbox services; the live invoker policies of the other services were not captured.
- The Cloud SQL instance `globus-db-qa` contains a database named `globus_staging`, a name that suggests a staging environment, which this project is not; the contents were not inspected.
- One of the two live API keys (the distance-matrix key) has an API target restriction but no referrer or other client restriction in the snapshot.
- The `Github` Cloud Build connection lists `ParticipantPortalPrototype`, a repository recorded elsewhere as archived.

## Known gaps

- Billing link state, Firestore, Certificate Manager, Cloud Deploy and IAP could not be read (APIs not enabled or billing API disabled), so they are recorded as none or unknown.
- Artifact Registry repo locations and VPC connector regions were inferred from usage or not captured, not read.
- IAM bindings, Cloud Run invoker policies and in-cluster Kubernetes state were not compared against Terraform, so IAM drift beyond the notes above is unknown.
- `collected_at` for the snapshot is approximate (2026-10-01); the live state may have moved since.
- The plan-time safety net is a heuristic: a reviewed change under the destroy threshold applies automatically on merge.
- Nothing here was verified against the Terraform state itself, only against `.tf` files and the live snapshot.
