---
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 server slots and client slots from the set `hp_slots` (`1` to `13`; slots 7 to 13 added 2026-11-09, Asana 1220345285726501, to pair with Globus QA 7 to 13; slots 4 to 6 added 2026-10-27, Asana 1219921325438868, to pair with Globus QA slots 4 to 6) (`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 12 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 13 slots (`qa-N.globus.allianceabroad.com`; slots 4 to 6 added 2026-10-20/21, Asana 1219713122269589 and 1219724649201067, and the set grown from 7 to 13 on 2026-11-09, Asana 1220345285726501) for `release/**` branches of `Alliance-Strategies/BackOfficePrototype` on `globus-cluster-qa`. Each slot gets its own Cloud SQL instance (`module.globus_db_qa_slot`, one per slot, Postgres 16, deletion protection enabled) and its own database (`globus_qa_1` to `globus_qa_6`) on the instance `globus-db-${local.globus_pool_env}-${each.key}`. `globus-db-qa` keeps the legacy `globus_staging`, which is still the bootstrap source for a fresh slot. The databases use `deletion_policy = "ABANDON"` so that Terraform never drops one while a branch is deployed to it. Because the database resources now point at the per-slot instances, the old `globus_qa_N` databases on `globus-db-qa` are left in place rather than dropped, as a fallback until the per-slot instances are proven. 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 provider's `attribute_condition` also admits the workflow `deploy-atlas-sandbox-qa.yml` pinned at `refs/heads/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. The file `globus-pool-slot5-import.tf` holds a one-shot `import` block that adopts the Cloud SQL instance `globus-db-qa-5` into state as `module.globus_db_qa_slot["5"].google_sql_database_instance.this`. The apply that added QA pool slots 5 and 6 (#418) was interrupted while creating that instance, so it existed in GCP but not in state, and later applies failed with `409 instanceAlreadyExists`. The file's comment says to remove it once an apply has succeeded.
- bop-front pool: `bop-front.tf` defines the QA deploy identity for `Alliance-Strategies/bop-front` (the Globus staff frontend). Per its header comments, the GKE rail runs the frontend as a second Deployment (`bop-front-web`) inside each BOP QA slot namespace `globus-qa-N`, with no Cloud Run service, no new load balancer, and no slot databases or hostnames of its own; the Cloud Run pool described at the end of this entry sits beside it, and a status comment at the top of the file marks that GKE-era text as history. Terraform creates an Artifact Registry repo `bop-front` (`module.ar_bop_front_qa`) and a deployer service account (`module.bop_front_deployer_qa`) that holds `roles/artifactregistry.writer` on that repo. It no longer holds `roles/container.developer` or the `gkehub` roles (GKE roles dropped 2026-10-15), and its description now reads `run.developer` on the `bop-front-qa-N` Cloud Run slots plus the Artifact Registry writer. The deployer also holds `roles/secretmanager.secretAccessor` on three build-time Vite secrets (`globus-vite-google-maps-api-key`, `globus-posthog-key`, `globus-posthog-host`) through `google_secret_manager_secret_iam_member.bop_front_deployer_qa_build_secrets`, and reads them at image build. Federation uses its own WIF pool `github-actions-bop-front`, which covers release branches (`deploy.yml`) and hotfix branches; hotfix refs may reach QA only through `trusted-deploy-qa.yml` pinned at `main`. `roles/iam.workloadIdentityUser` on the deployer is granted to the pool's principal set for that repository. The output `bop_front_qa_deploy_identity` lists the values bop-front's `deploy.yml` needs for QA. The header comment says the Artifact Registry repo and WIF pool are reused as is. ADR 0005 adds a Cloud Run pool of three slots (`google_cloud_run_v2_service.bop_front_pool`) in `us-central1`, because domain mappings are not allowed in `northamerica-northeast1`. Terraform sets a bootstrap image only; CI (`deploy.yml` on `release/**`) owns image, env, labels and traffic. The slots run as a runtime service account (`module.bop_front_runtime_qa`) that has no project roles and `secretAccessor` on exactly the five secrets the BFF loads (the `bop-front-*` and `globus-entra-*` secrets). The five are the portal's own two secrets plus the Hub's Entra app registration, which the portal reuses with its own redirect URIs; the portal-owned values are added out of band, never in Terraform. Each slot has its own hostname `qa-N.staff.allianceabroad.com`, mapped by Terraform through `module.bop_front_qa_domain_mapping` and paired with the Globus QA slot of the same number. The output `bop_front_qa_pool` lists the slot services, runtime identity and hostnames.
- Applicant Portal Keycloak slots: `applicant-keycloak-slots.tf` and `applicant-keycloak-slot-services.tf` add slots next to slot 1 (the `applicant_kc_slots` set is `2` to `13`), which is the existing `applicant-keycloak` service (`module.keycloak_qa`, unchanged). Each slot has its own database (`applicant_kc_qa_2` to `applicant_kc_qa_6`) on the existing `keycloak-db-qa` instance, declared with `deletion_policy = "ABANDON"`. The slot services (`applicant-keycloak-qa-2` to `applicant-keycloak-qa-6`, `module.keycloak_qa_slot`) run in `northamerica-northeast1`, set `KC_DB_POOL_MAX_SIZE` to `15` and are limited to `max_instances = 1`. The private scratch bucket `applicant-kc-slot-copy-qa` (`public_access_prevention` enforced, objects expire after one day) holds the Cloud SQL export/import used to copy slot 1's database into each slot; the Cloud SQL service agent holds `roles/storage.objectAdmin` on it. The set `applicant_kc_service_slots` is `2` to `13`. In Terraform, `keycloak-db-qa` (`module.keycloak_db_qa`) uses tier `db-custom-2-7680` with the database flag `max_connections` set to `250` (changed 2026-11-09), sized for 12 slot Keycloaks at `KC_DB_POOL_MAX_SIZE=15` plus slot 1; the live Cloud SQL table below is the 2026-10-01 snapshot and still shows `db-g1-small`.
- 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`. The slot sets `ppp2_slots` (in `ppp2-pool.tf`) and `wildpacks_slots` (in `wildpacks.tf`) are each `1` to `6`.
- Partner Portal pool: `partner-portal-pool.tf` defines one Cloud Run slot in `us-central1`. Per its header, `release/*` branches deploy to the slot with a 24 h hold and `hotfix/*` may use it for 2 h. The slot runs as a runtime identity described as `secretAccessor` on the `partner-portal-qa-*` secrets only, and the deployer is described as `run.developer` on the pool slots plus Artifact Registry writer only. Federation uses its own WIF pool `github-actions-partner-portal`, described as federation for `Alliance-Strategies/PartnerPortal` only. Sign-in is Auth0, which replaced Clerk on 2026-10-27, with one dedicated Auth0 tenant per slot (replacing the shared `hostqa` app; QA slot 1 uses `partner-qa-1.us.auth0.com`): `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID` and `AUTH0_AUDIENCE` are set on the slot as plain env values from Terraform locals, not as secrets, with the domain and client ID looked up per slot in `local.partner_portal_auth0`. The app has no database of its own, and the Globus URL configured for the pool is `https://qa-1.globus.allianceabroad.com`. The custom domain `qa-1.partners.allianceabroad.com` is not managed by Terraform.
- Agreements Service pool: `agreements-service.tf` defines one Cloud Run slot in `us-central1`, plus the original Cloud SQL instance `agreements-db-qa` (Postgres 17, private IP, `REGIONAL` availability for the Cloud SQL failover drill window; the file's comment says it goes back to the ZONAL default by PR once the drill is recorded), an events topic with a DLQ topic, an artifacts bucket and Secret Manager containers. QA keeps point-in-time recovery on for that instance. The original instance was replaced by the CMEK-encrypted `agreements-db-qa-2` on 2026-10-28, and `module.agreements_db` and `google_sql_database.agreements` are now `removed` blocks with `destroy = false`, so Terraform forgets them without destroying anything. Per the file's comment, the instance itself is deleted out of band once the slot has served from the successor. A second Cloud SQL instance, `agreements-db-qa-2` (`module.agreements_db_cmek`), is the CMEK-encrypted successor with the same shape as `agreements-db-qa` apart from the tier, which is `db-custom-1-3840` now that the load window is closed; it is a separate instance because CMEK is set at instance creation only. Its `encryption_key_name` is the crypto key `google_kms_crypto_key.agreements_cloud_sql` (the Cloud SQL disk and backups key, rotation period 90 days, `prevent_destroy = true`), and the Cloud SQL service agent holds `roles/cloudkms.cryptoKeyEncrypterDecrypter` on that key through `google_kms_crypto_key_iam_member.agreements_cloud_sql_agent`. Its database-url secret version is added out of band. The slot has no `allUsers` grant; callers are OIDC only. The Globus Hub pool (one runtime service account for every slot) calls the service with a Google OIDC token, and `google_cloud_run_v2_service_iam_member.agreements_slot_hub_runtime_invoker` grants `roles/run.invoker` on each slot to `globus-qa-runtime`. Terraform ignores changes to the slot's service-level `scaling` block, which the API reports (`manual_instance_count` / `min_instance_count` = 0) although the config never declares it; without that, every apply would plan an in-place no-op update. Template scaling is still managed. Federation uses its own WIF pool `github-actions-agreements`, described as federation for `Alliance-Strategies/agreements-service` only. The runtime identity is described as `cloudsql.client`, publisher on `agreements-events-qa`, `objectAdmin` on `agreements-artifacts-qa` and `secretAccessor` on the `agreements-qa-*` secrets. The deployer is described as `deploy.yml` at `release/*` or `trusted-deploy-qa.yml@main`, with `run.developer` + `run.invoker` on the pool slots and AR writer. The provider's `attribute_condition` also admits `load-soak.yml` pinned to a `refs/heads/release/` workflow ref, with the same ref and workflow pinning as `deploy.yml`; the 1-hour soak runs from GitHub Actions against a QA slot as the QA deployer, read-only (it mints the slot identity token and sends traffic). It also holds `roles/iam.serviceAccountTokenCreator` on its own service account (`google_service_account_iam_member.agreements_deployer_self_id_token`) so the post-deploy smoke can mint the deployer's own OIDC identity token; the binding's member is the account itself. The custom domain `qa-N.agreements.allianceabroad.com` is not managed by Terraform. The output `agreements_qa_runtime_contract` lists what the deploy wires into each QA slot. Terraform also creates a rendering worker Cloud Run service (`google_cloud_run_v2_service.agreements_worker`) named `agreements-worker-<env>-<slot>`; the release rail deploys these next to each QA pool slot with `SERVICE_ROLE=worker`, while the slot itself runs `SERVICE_ROLE=api`. Each worker runs one instance with CPU always allocated. The load window is closed: `agreements_load_slot` is `null`, which puts every slot on the normal shape, and `agreements_load_shape` is `{ min = 4, max = 6, concurrency = 40 }`. The maximum of 6 instances is set so that the slot, the worker and the outbox listeners stay under the database tier's 100 `max_connections`. The apply identity (`module.terraform_tf_qa`) holds `roles/cloudkms.admin` (added 2026-10-27) for key rings, crypto keys and key IAM of the agreements CMEK, and `google_kms_key_ring.agreements` has `depends_on = [module.terraform_tf_qa]` so the ring is created after that binding.
- Platform Pulse alert channel: `monitoring-pulse-channel.tf` creates the topic `monitoring-alerts-qa` (`module.monitoring_alerts_topic`) with a push subscription `monitoring-alerts-qa-pulse` that pushes to the `/pubsub` path of the `monitoring-pulse-bridge` service, which is the single bridge instance in the development project. The push authenticates with OIDC as `module.monitoring_pulse_push`. A Cloud Monitoring notification channel of type `pubsub` named `Platform Pulse (Teams, via monitoring-pulse-bridge)` (`google_monitoring_notification_channel.platform_pulse`) points at that topic. The Cloud Monitoring notification service agent is granted `roles/pubsub.publisher` on the topic, and the channel ID is exposed as the output `platform_pulse_notification_channel_id`.
- WIF subject mapping: the GitHub providers in `bop-front.tf`, `branching-sandbox.tf`, `globus-pool.tf`, `host-portal-pool.tf`, `ppp2-pool.tf` and `wildpacks.tf` map `google.subject` to `local.wif_subject_bounded`, defined in `wif-subject.tf`, because Google IAM refuses a mapped `google.subject` over 127 bytes. A `sub` of 127 bytes or less is mapped unchanged; a longer one falls back to `repo:` plus the repository, then `:ref:` plus the ref truncated at the first `-`. Each provider's `attribute_condition` still reads the full `assertion.ref` and `assertion.job_workflow_ref`.

_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_container_viewer` | `google_project_iam_member` | `roles/container.viewer` | `main.tf` |
| `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`, `cloudscheduler`, `orgpolicy`, `distance-matrix-backend`) are not declared. `cloudkms` was in this group at snapshot time and is now declared in `module.project_services_qa`.
- 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.
