Skip to content

feat(infra): deploy web app to Vercel with CI workflow and SSM-backed env vars#456

Closed
tataihono wants to merge 3 commits intomainfrom
feat/68-web-vercel-deploy
Closed

feat(infra): deploy web app to Vercel with CI workflow and SSM-backed env vars#456
tataihono wants to merge 3 commits intomainfrom
feat/68-web-vercel-deploy

Conversation

@tataihono
Copy link
Copy Markdown
Contributor

@tataihono tataihono commented Mar 13, 2026

Summary

Add a complete Vercel deployment pipeline for the web app, modeled after the CMS deploy pattern. Includes a GitHub Actions workflow (web-deploy.yml) with Turbo-based affected detection, IAM role assumption for Vercel API token from SSM, and Vercel CLI build/deploy. Supports PR preview deployments (with PR comment), stage branch previews, and main branch production deploys.

All Vercel project-level environment variables are sourced exclusively from the /forge/aws/web/ SSM namespace via Terraform. A dedicated web deploy IAM role reads the Vercel API token from SSM. The Vercel project config, GitHub environments, secrets, and variables are fully Terraform-managed. SSM params for VERCEL_ORG_ID and per-project IDs (e.g. web_project_id) under /forge/vercel/ allow future multi-project support.

Resolves #68

Contracts Changed

  • yes
  • no

Regeneration Required

  • yes
  • no

Validation

  • Contracts validated
  • Generated code verified (no manual edits)
  • Tests and build passed
  • Terraform plan reviewed (if infra change)

Post-merge manual steps

  1. Set /forge/vercel/org_id and /forge/vercel/web_project_id values in AWS console
  2. Apply infra/aws for both stage and prod (creates SSM params + IAM roles)
  3. Apply infra/github (creates environments + wires secrets/variables)
  4. Apply infra/vercel (syncs env vars to Vercel project)

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Preview deployments with preview URLs posted to pull requests; commit comments report non-PR deployments.
  • Chores
    • Automated web deployment flow enabled for preview, stage, and production environments.
    • Infrastructure and environment configuration updated to support web builds, deploys, and runtime env vars for preview and production.

- Expand infra/vercel with root_directory, STRAPI_PREVIEW_SECRET,
  NEXT_PUBLIC_GRAPHQL_URL, NEXT_PUBLIC_CMS_HOSTNAME env vars
- Source all Vercel env vars from /forge/aws/web/ SSM namespace
- Add NEXT_PUBLIC_GRAPHQL_URL and NEXT_PUBLIC_CMS_HOSTNAME deploy
  SSM params to web module, derived from cms_domain_name
- Create web deploy IAM role with Vercel API token SSM read access
- Add org_id and web_project_id SSM params under /forge/vercel/
- Update Vercel Terraform stack roles to use web SSM paths/KMS keys
- Add web-preview, web-stage, web-prod GitHub environments
- Wire WEB_DEPLOY_ROLE_ARN secrets and VERCEL_* variables via Terraform

Resolves #68

Made-with: Cursor
Vercel CLI deploy triggered on push to stage/main and pull_request.
Turbo-based affected detection for @forge/web. Reads Vercel API token
from SSM via IAM role. PR previews post/update a comment with the
deploy URL. Stage and main deploys post commit comments.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab133795-bec0-4c56-a610-c24192ca6f2a

📥 Commits

Reviewing files that changed from the base of the PR and between 8228064 and 514e197.

📒 Files selected for processing (4)
  • infra/github/actions.tf
  • infra/github/data.tf
  • infra/github/environments.tf
  • infra/github/variables.tf

Walkthrough

Adds a web deployment pipeline: a GitHub Actions workflow that uses Turbo to detect affected web changes, assumes an AWS IAM role via OIDC to read Vercel secrets from SSM, and deploys to Vercel (preview/stage/prod) with PR/commit status comments. Terraform adds IAM role, SSM params, GitHub envs/secrets, and Vercel project wiring.

Changes

Cohort / File(s) Summary
GitHub Actions Workflow
\.github/workflows/web-deploy.yml
New workflow that detects affected web changes via Turbo, conditionally assumes AWS role, retrieves Vercel token from SSM, deploys with Vercel CLI, and posts PR/commit deployment comments.
AWS IAM & Web Deploy Role
infra/aws/github/web.tf, infra/aws/github/outputs.tf, infra/aws/github/ssm.tf
Adds github_actions_web_deploy IAM role with OIDC assume-role policy, SSM parameter storing role ARN, and output exposing the role ARN.
AWS KMS & Terraform wiring
infra/aws/github/terraform.tf
Adds web SSM KMS data sources (stage/prod) and updates stacks to reference web SSM parameter/KMS ARNs and Vercel param ARNs.
Web module inputs & SSM params
infra/aws/modules/web/variables.tf, infra/aws/modules/web/ssm_deploy.tf, infra/aws/modules/platform/main.tf
Adds cms_domain_name variable and creates SSM params for NEXT_PUBLIC_GRAPHQL_URL and NEXT_PUBLIC_CMS_HOSTNAME derived from CMS domain; passes cms_domain_name into web module.
Vercel SSM & Project config
infra/vercel/ssm.tf, infra/vercel/data.tf, infra/vercel/main.tf
Adds org_id and web_project_id SSM params; switches Strapi token lookups to web namespace; adds STRAPI_PREVIEW_SECRET, NEXT_PUBLIC_GRAPHQL_URL, NEXT_PUBLIC_CMS_HOSTNAME env vars; sets project root_directory.
GitHub repo envs, secrets & vars
infra/github/environments.tf, infra/github/actions.tf, infra/github/data.tf, infra/github/variables.tf
Creates web-preview, web-stage, web-prod environments; adds environment secrets WEB_DEPLOY_ROLE_ARN per env and repository variables for Vercel org/project IDs; adds enable_web_deploy flag and corresponding SSM data lookups.
Miscellaneous outputs/data updates
infra/aws/github/outputs.tf, infra/vercel/data.tf
New Terraform output for web deploy role ARN; adds new web-related SSM data sources (STRAPI_PREVIEW_SECRET, NEXT_PUBLIC_*).

Sequence Diagram(s)

sequenceDiagram
    participant GH as GitHub
    participant Workflow as web-deploy Workflow
    participant Turbo as Turbo CLI
    participant AWS as AWS (IAM/SSM)
    participant Vercel as Vercel API
    participant GHComment as GitHub (Comments)

    GH->>Workflow: Trigger (pull_request / push / workflow_dispatch)
    Workflow->>Turbo: Run lint --affected --dry-run=json
    alt Affected Detected
        Turbo-->>Workflow: web=true
    else Detection failed or web affected unknown
        Turbo-->>Workflow: error/empty
        Workflow->>Workflow: fallback -> force deploy
    end

    Workflow->>Workflow: Choose target env (preview / stage / prod)
    Workflow->>AWS: Assume role via OIDC (github_actions_web_deploy)
    AWS-->>Workflow: Temporary credentials
    Workflow->>AWS: Get Vercel token from SSM
    AWS-->>Workflow: Vercel token
    Workflow->>Vercel: Deploy/build via Vercel CLI (token)
    Vercel-->>Workflow: Deployment status & URL

    alt Event is Pull Request
        Workflow->>GHComment: Post/update PR comment with preview URL
    else Push to branch
        Workflow->>GHComment: Post commit comment with deployment details
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • up-tandem
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: adding Vercel deployment infrastructure with CI workflow and environment variable management via SSM.
Linked Issues check ✅ Passed All acceptance criteria from issue #68 are met: Vercel infra in infra/vercel, PR previews, stage/main mappings, SSM-backed secrets, Turbo gating, and environment protection/approval are implemented.
Out of Scope Changes check ✅ Passed All changes align with the scope of implementing Vercel deployment with CI workflow and SSM-backed environment variables. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/68-web-vercel-deploy
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

❌ Terraform plan (vercel) — Plan failed

Changes: +0 ~1 -0
Time:

TZ Time
UTC 2026-03-13, 3:56:05 a.m.
NZ 2026-03-13, 4:56:05 p.m.
PT 2026-03-12, 8:56:05 p.m.
ET 2026-03-12, 11:56:05 p.m.

Run: https://github.com/JesusFilm/forge/actions/runs/23035459861

data.aws_ssm_parameter.strapi_preview_secret_prod: Reading...
data.aws_ssm_parameter.next_public_cms_hostname_stage: Reading...
data.aws_ssm_parameter.api_token: Reading...
data.aws_ssm_parameter.next_public_graphql_url_stage: Reading...
data.aws_ssm_parameter.strapi_api_token_stage: Reading...
data.aws_ssm_parameter.next_public_cms_hostname_prod: Reading...
data.aws_ssm_parameter.strapi_preview_secret_stage: Reading...
data.aws_ssm_parameter.next_public_graphql_url_prod: Reading...
data.aws_ssm_parameter.strapi_api_token_prod: Reading...
data.aws_ssm_parameter.api_token: Read complete after 0s [id=/forge/vercel/api_token]
vercel_project.web: Refreshing state... [id=prj_bkpwmtIJtBAvAvDXVIS5G9fWLQh4]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform planned the following actions, but then encountered a problem:

  # vercel_project.web will be updated in-place
  ~ resource "vercel_project" "web" {
      ~ build_machine_type                                = "turbo" -> (known after apply)
      + enable_preview_feedback                           = (known after apply)
      + git_provider_options                              = (known after apply)
        id                                                = "prj_bkpwmtIJtBAvAvDXVIS5G9fWLQh4"
        name                                              = "forge-web"
      + preview_comments                                  = (known after apply)
      + protection_bypass_for_automation_secret           = (sensitive value)
      ~ resource_config                                   = {
          ~ fluid                     = true -> (known after apply)
          + function_default_cpu_type = (known after apply)
          ~ function_default_regions  = [
              - "iad1",
            ] -> (known after apply)
          + function_default_timeout  = (known after apply)
        } -> (known after apply)
      + root_directory                                    = "apps/web"
      ~ serverless_function_region                        = "iad1" -> (known after apply)
      + team_id                                           = (known after apply)
        # (16 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Error: reading SSM Parameter (/forge/aws/web/stage/STRAPI_API_TOKEN): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 18485b3f-460b-46c8-8ab4-fbe200206b7b, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/stage/STRAPI_API_TOKEN because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.strapi_api_token_stage,
  on data.tf line 8, in data "aws_ssm_parameter" "strapi_api_token_stage":
   8: data "aws_ssm_parameter" "strapi_api_token_stage" {


Error: reading SSM Parameter (/forge/aws/web/prod/STRAPI_API_TOKEN): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: c2947ec2-bf11-4bdd-ad51-39a6da886190, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/prod/STRAPI_API_TOKEN because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.strapi_api_token_prod,
  on data.tf line 13, in data "aws_ssm_parameter" "strapi_api_token_prod":
  13: data "aws_ssm_parameter" "strapi_api_token_prod" {


Error: reading SSM Parameter (/forge/aws/web/stage/STRAPI_PREVIEW_SECRET): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 6484c102-a918-44a7-b916-f461d9e9e140, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/stage/STRAPI_PREVIEW_SECRET because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.strapi_preview_secret_stage,
  on data.tf line 18, in data "aws_ssm_parameter" "strapi_preview_secret_stage":
  18: data "aws_ssm_parameter" "strapi_preview_secret_stage" {


Error: reading SSM Parameter (/forge/aws/web/prod/STRAPI_PREVIEW_SECRET): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 9543275f-e9bf-46de-b3c2-1dd5125e6b27, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/prod/STRAPI_PREVIEW_SECRET because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.strapi_preview_secret_prod,
  on data.tf line 23, in data "aws_ssm_parameter" "strapi_preview_secret_prod":
  23: data "aws_ssm_parameter" "strapi_preview_secret_prod" {


Error: reading SSM Parameter (/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: beb7ac64-5cc5-49c0-ba7d-5a33600568a7, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.next_public_graphql_url_stage,
  on data.tf line 28, in data "aws_ssm_parameter" "next_public_graphql_url_stage":
  28: data "aws_ssm_parameter" "next_public_graphql_url_stage" {


Error: reading SSM Parameter (/forge/aws/web/prod/NEXT_PUBLIC_GRAPHQL_URL): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 836dadfa-d191-4c02-8b83-a29786554b83, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/prod/NEXT_PUBLIC_GRAPHQL_URL because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.next_public_graphql_url_prod,
  on data.tf line 32, in data "aws_ssm_parameter" "next_public_graphql_url_prod":
  32: data "aws_ssm_parameter" "next_public_graphql_url_prod" {


Error: reading SSM Parameter (/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 3ff4249b-9eb8-4f2a-8fa1-be5a0ced7c77, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.next_public_cms_hostname_stage,
  on data.tf line 36, in data "aws_ssm_parameter" "next_public_cms_hostname_stage":
  36: data "aws_ssm_parameter" "next_public_cms_hostname_stage" {


Error: reading SSM Parameter (/forge/aws/web/prod/NEXT_PUBLIC_CMS_HOSTNAME): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 671e5ef2-a210-4f8c-af5c-3064c11a9303, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-vercel-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/aws/web/prod/NEXT_PUBLIC_CMS_HOSTNAME because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.next_public_cms_hostname_prod,
  on data.tf line 40, in data "aws_ssm_parameter" "next_public_cms_hostname_prod":
  40: data "aws_ssm_parameter" "next_public_cms_hostname_prod" {

::error::Terraform exited with code 1.

@github-actions
Copy link
Copy Markdown

❌ Terraform plan (github/prod) — Plan failed

Changes: +3 ~0 -0
Time:

TZ Time
UTC 2026-03-13, 3:56:09 a.m.
NZ 2026-03-13, 4:56:09 p.m.
PT 2026-03-12, 8:56:09 p.m.
ET 2026-03-12, 11:56:09 p.m.

Run: https://github.com/JesusFilm/forge/actions/runs/23035459861

data.aws_ssm_parameter.terraform_aws_role_apply_arn_prod: Reading...
data.aws_ssm_parameter.cms_deploy_role_arn_prod: Reading...
data.aws_ssm_parameter.terraform_aws_role_plan_arn_prod: Reading...
data.aws_ssm_parameter.terraform_aws_role_apply_arn_stage: Reading...
data.aws_ssm_parameter.terraform_aws_role_plan_arn_stage: Reading...
data.aws_ssm_parameter.vercel_org_id: Reading...
data.aws_ssm_parameter.terraform_github_role_plan_arn: Reading...
data.aws_ssm_parameter.github_app_pem: Reading...
data.aws_ssm_parameter.github_installation_id: Reading...
data.aws_ssm_parameter.strapi_api_token_stage: Reading...
data.aws_ssm_parameter.terraform_github_role_plan_arn: Read complete after 0s [id=/forge/github/terraform_github_role_plan_arn]
data.aws_ssm_parameter.terraform_github_role_apply_arn: Reading...
data.aws_ssm_parameter.terraform_aws_role_apply_arn_prod: Read complete after 0s [id=/forge/github/terraform_aws_role_apply_prod_arn]
data.aws_ssm_parameter.vercel_web_project_id: Reading...
data.aws_ssm_parameter.terraform_aws_role_plan_arn_prod: Read complete after 0s [id=/forge/github/terraform_aws_role_plan_prod_arn]
data.aws_ssm_parameter.terraform_vercel_role_plan_arn: Reading...
data.aws_ssm_parameter.terraform_aws_role_apply_arn_stage: Read complete after 0s [id=/forge/github/terraform_aws_role_apply_stage_arn]
data.aws_ssm_parameter.github_app_id: Reading...
data.aws_ssm_parameter.cms_deploy_role_arn_stage: Reading...
data.aws_ssm_parameter.cms_deploy_role_arn_prod: Read complete after 0s [id=/forge/github/cms_deploy_role_arn_prod]
data.aws_ssm_parameter.terraform_aws_role_plan_arn_stage: Read complete after 0s [id=/forge/github/terraform_aws_role_plan_stage_arn]
data.aws_ssm_parameter.web_deploy_role_arn_prod: Reading...
data.aws_ssm_parameter.terraform_vercel_role_apply_arn: Reading...
data.aws_ssm_parameter.github_installation_id: Read complete after 0s [id=/forge/github/installation_id]
data.aws_ssm_parameter.web_deploy_role_arn_stage: Reading...
data.aws_ssm_parameter.github_app_pem: Read complete after 0s [id=/forge/github/app_private_key]
data.aws_ssm_parameter.strapi_api_token_stage: Read complete after 0s [id=/forge/aws/cms/stage/STRAPI_INTERNAL_API_TOKEN]
data.aws_ssm_parameter.terraform_github_role_apply_arn: Read complete after 0s [id=/forge/github/terraform_github_role_apply_arn]
data.aws_ssm_parameter.terraform_vercel_role_plan_arn: Read complete after 0s [id=/forge/github/terraform_vercel_role_plan_arn]
data.aws_ssm_parameter.github_app_id: Read complete after 0s [id=/forge/github/app_id]
data.aws_ssm_parameter.cms_deploy_role_arn_stage: Read complete after 0s [id=/forge/github/cms_deploy_role_arn_stage]
data.aws_ssm_parameter.terraform_vercel_role_apply_arn: Read complete after 0s [id=/forge/github/terraform_vercel_role_apply_arn]
github_repository.forge: Refreshing state... [id=forge]
github_repository_environment.github_plan: Refreshing state... [id=forge:github-plan]
github_repository_environment.aws_plan_stage: Refreshing state... [id=forge:aws-plan-stage]
github_repository_environment.vercel_plan: Refreshing state... [id=forge:vercel-plan]
github_repository_environment.aws_prod: Refreshing state... [id=forge:aws-prod]
github_repository_environment.aws_plan_prod: Refreshing state... [id=forge:aws-plan-prod]
github_repository_environment.github_prod: Refreshing state... [id=forge:github-prod]
github_repository_environment.vercel_prod: Refreshing state... [id=forge:vercel-prod]
github_repository_environment.aws_stage: Refreshing state... [id=forge:aws-stage]
github_branch_default.forge: Refreshing state... [id=forge]
github_repository_environment.cms_prod: Refreshing state... [id=forge:cms-prod]
github_actions_variable.aws_region: Refreshing state... [id=forge:AWS_REGION]
github_repository_environment.cms_stage: Refreshing state... [id=forge:cms-stage]
github_actions_secret.strapi_api_token: Refreshing state... [id=forge:STRAPI_API_TOKEN]
github_actions_environment_secret.github_terraform_role_plan: Refreshing state... [id=forge:github-plan:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.aws_plan_role_stage: Refreshing state... [id=forge:aws-plan-stage:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.vercel_terraform_role_plan: Refreshing state... [id=forge:vercel-plan:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.terraform_apply_role_prod: Refreshing state... [id=forge:aws-prod:TERRAFORM_APPLY_ROLE_ARN]
github_actions_environment_secret.github_terraform_role_apply: Refreshing state... [id=forge:github-prod:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.aws_plan_role_prod: Refreshing state... [id=forge:aws-plan-prod:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.vercel_terraform_role_apply: Refreshing state... [id=forge:vercel-prod:TERRAFORM_ROLE_ARN]
github_actions_environment_secret.terraform_apply_role_stage: Refreshing state... [id=forge:aws-stage:TERRAFORM_APPLY_ROLE_ARN]
github_actions_environment_secret.cms_deploy_role_prod: Refreshing state... [id=forge:cms-prod:CMS_DEPLOY_ROLE_ARN]
github_actions_environment_secret.cms_deploy_role_stage: Refreshing state... [id=forge:cms-stage:CMS_DEPLOY_ROLE_ARN]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform planned the following actions, but then encountered a problem:

  # github_repository_environment.web_preview will be created
  + resource "github_repository_environment" "web_preview" {
      + can_admins_bypass   = true
      + environment         = "web-preview"
      + id                  = (known after apply)
      + prevent_self_review = false
      + repository          = "forge"
    }

  # github_repository_environment.web_prod will be created
  + resource "github_repository_environment" "web_prod" {
      + can_admins_bypass   = true
      + environment         = "web-prod"
      + id                  = (known after apply)
      + prevent_self_review = false
      + repository          = "forge"
    }

  # github_repository_environment.web_stage will be created
  + resource "github_repository_environment" "web_stage" {
      + can_admins_bypass   = true
      + environment         = "web-stage"
      + id                  = (known after apply)
      + prevent_self_review = false
      + repository          = "forge"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Error: reading SSM Parameter (/forge/github/web_deploy_role_arn_stage): couldn't find resource

  with data.aws_ssm_parameter.web_deploy_role_arn_stage,
  on data.tf line 61, in data "aws_ssm_parameter" "web_deploy_role_arn_stage":
  61: data "aws_ssm_parameter" "web_deploy_role_arn_stage" {


Error: reading SSM Parameter (/forge/github/web_deploy_role_arn_prod): couldn't find resource

  with data.aws_ssm_parameter.web_deploy_role_arn_prod,
  on data.tf line 65, in data "aws_ssm_parameter" "web_deploy_role_arn_prod":
  65: data "aws_ssm_parameter" "web_deploy_role_arn_prod" {


Error: reading SSM Parameter (/forge/vercel/org_id): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: 46d71241-aff9-4a05-949a-ba32ff398b11, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-github-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/org_id because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.vercel_org_id,
  on data.tf line 69, in data "aws_ssm_parameter" "vercel_org_id":
  69: data "aws_ssm_parameter" "vercel_org_id" {


Error: reading SSM Parameter (/forge/vercel/web_project_id): operation error SSM: GetParameter, https response error StatusCode: 400, RequestID: ac385f63-18a9-4815-8a50-1b2c7cc61fa3, api error AccessDeniedException: User: arn:aws:sts::031374266475:assumed-role/forge-github-actions-terraform-github-plan/GitHubActions is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/web_project_id because no identity-based policy allows the ssm:GetParameter action

  with data.aws_ssm_parameter.vercel_web_project_id,
  on data.tf line 73, in data "aws_ssm_parameter" "vercel_web_project_id":
  73: data "aws_ssm_parameter" "vercel_web_project_id" {

::error::Terraform exited with code 1.

@github-actions
Copy link
Copy Markdown

🟨 Terraform plan (aws/prod) — Changes detected

Changes: +7 ~4 -0
Time:

TZ Time
UTC 2026-03-13, 3:56:20 a.m.
NZ 2026-03-13, 4:56:20 p.m.
PT 2026-03-12, 8:56:20 p.m.
ET 2026-03-12, 11:56:20 p.m.

Run: https://github.com/JesusFilm/forge/actions/runs/23035459861

module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Opening...
module.platform.module.application.ephemeral.random_password.app_key_4: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Opening...
module.platform.module.application.ephemeral.random_password.api_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.preview_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Opening...
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.app_key_4: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.preview_secret: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.app_key_2: Opening...
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.api_token_salt: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.app_key_1: Opening...
module.platform.module.application.ephemeral.random_password.app_key_3: Opening...
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Opening...
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.app_key_2: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_3: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Opening...
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_1: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.jwt_secret: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.encryption_key: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Opening...
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Opening complete after 0s
module.platform.module.assets.aws_acm_certificate.assets: Refreshing state... [id=arn:aws:acm:us-east-1:031374266475:certificate/ab733e08-7e38-4631-9576-ee73d0afee13]
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Opening...
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.encryption_key: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Opening complete after 0s
module.github.aws_kms_key.github_ssm[0]: Refreshing state... [id=b3aebd98-e778-445f-8e66-f23b48dae25c]
module.platform.module.application.aws_iam_role.ecs_task: Refreshing state... [id=forge-cms-prod-task-role]
module.platform.module.assets.aws_s3_bucket.assets_access_logs: Refreshing state... [id=forge-cms-prod-assets-access-logs]
module.platform.module.assets.aws_kms_key.assets: Refreshing state... [id=52863204-2209-4678-9c47-151c5cdf08d6]
module.platform.module.application.aws_kms_key.cms_ssm: Refreshing state... [id=ce229066-4018-4d88-8843-4aa9d08e7e47]
module.github.aws_ssm_parameter.github_app_id[0]: Refreshing state... [id=/forge/github/app_id]
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Opening complete after 0s
module.platform.module.application.aws_cloudwatch_log_group.cms: Refreshing state... [id=/ecs/forge-cms-prod]
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Opening complete after 0s
module.platform.aws_vpc.platform: Refreshing state... [id=vpc-05ef5d31814417688]
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Opening complete after 0s
module.vercel.aws_kms_key.vercel_ssm[0]: Refreshing state... [id=37e33c00-2088-4cb5-85a4-7136e2b364b1]
module.platform.aws_cloudwatch_log_group.waf: Refreshing state... [id=aws-waf-logs-forge-platform-prod]
data.aws_caller_identity.current: Reading...
module.iam[0].module.groups.module.require_mfa.data.aws_caller_identity.current: Reading...
aws_route53_zone.forge[0]: Refreshing state... [id=Z078031724OHPW03IKVLO]
module.github.aws_ssm_parameter.github_installation_id[0]: Refreshing state... [id=/forge/github/installation_id]
module.iam[0].module.groups.module.admin_readonly.aws_iam_group.admin_readonly: Refreshing state... [id=forge-admin-readonly]
module.iam[0].module.groups.module.require_mfa.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.iam[0].module.groups.module.dev_secrets.aws_iam_group.dev_secrets: Refreshing state... [id=forge-dev-secrets]
module.github.data.aws_kms_alias.vercel_ssm_for_web: Reading...
data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.iam[0].module.groups.module.login_profile.aws_iam_group.login_profile: Refreshing state... [id=forge-iam-login-profile]
module.iam[0].module.groups.module.billing.aws_iam_group.billing: Refreshing state... [id=forge-billing]
module.github.data.aws_kms_alias.vercel_ssm_for_web: Read complete after 1s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-vercel-prod-ssm]
module.iam[0].module.groups.module.dev_secrets.data.aws_caller_identity.current: Reading...
module.platform.module.assets.data.aws_caller_identity.current: Reading...
module.platform.data.aws_availability_zones.available: Reading...
module.iam[0].module.groups.module.dev_secrets.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.aws_ses_domain_identity.forge[0]: Refreshing state... [id=forge.jesusfilm.org]
module.platform.module.assets.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.github.data.aws_kms_alias.cms_ssm_prod[0]: Reading...
module.iam[0].module.groups.module.billing.data.aws_iam_policy_document.billing: Reading...
module.iam[0].module.groups.module.billing.data.aws_iam_policy_document.billing: Read complete after 0s [id=3512268210]
module.platform.data.aws_caller_identity.current: Reading...
data.aws_dynamodb_table.terraform_state_lock: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_apply: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_apply: Read complete after 0s [id=1914455514]
module.iam[0].module.groups.module.login_profile.data.aws_caller_identity.current: Reading...
module.platform.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.github.data.aws_kms_alias.web_ssm_stage[0]: Reading...
module.platform.aws_eip.nat: Refreshing state... [id=eipalloc-0bfddde8d0da76aa6]
module.iam[0].module.groups.module.login_profile.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.module.assets.aws_s3_bucket.assets: Refreshing state... [id=forge-cms-prod-assets]
module.github.data.aws_kms_alias.cms_ssm_prod[0]: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-cms-prod-ssm]
module.platform.module.application.aws_acm_certificate.alb: Refreshing state... [id=arn:aws:acm:us-east-2:031374266475:certificate/63f93430-798e-4379-b0b5-d65255199a29]
module.platform.data.aws_availability_zones.available: Read complete after 0s [id=us-east-2]
module.platform.module.application.aws_iam_role.ecs_execution: Refreshing state... [id=forge-cms-prod-execution-role]
module.github.data.aws_kms_alias.web_ssm_stage[0]: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-web-stage-ssm]
module.github.data.aws_caller_identity.current: Reading...
module.platform.module.application.aws_ecs_cluster.cms: Refreshing state... [id=arn:aws:ecs:us-east-2:031374266475:cluster/forge-cms-prod]
module.github.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.module.assets.aws_cloudfront_origin_access_control.assets: Refreshing state... [id=E1TD5D8QKH7E2K]
module.platform.aws_wafv2_web_acl.alb: Refreshing state... [id=5d527a2d-be37-446e-b4ed-53dd11c94e8a]
module.platform.module.application.aws_ecr_repository.cms: Refreshing state... [id=forge-cms-prod]
module.github.aws_iam_openid_connect_provider.github_actions: Refreshing state... [id=arn:aws:iam::031374266475:oidc-provider/token.actions.githubusercontent.com]
module.platform.aws_s3_bucket.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.github.data.aws_kms_alias.web_ssm_prod[0]: Reading...
module.github.data.aws_kms_alias.terraform_state: Reading...
module.github.data.aws_kms_alias.cms_ssm_stage[0]: Reading...
data.aws_dynamodb_table.terraform_state_lock: Read complete after 0s [id=forge-terraform-locks]
module.github.aws_ssm_parameter.github_app_pem[0]: Refreshing state... [id=/forge/github/app_private_key]
module.github.aws_kms_alias.github_ssm[0]: Refreshing state... [id=alias/forge-github-prod-ssm]
module.github.data.aws_kms_alias.web_ssm_prod[0]: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-web-prod-ssm]
module.platform.module.assets.aws_kms_alias.assets: Refreshing state... [id=alias/forge-cms-prod-assets]
module.github.data.aws_kms_alias.terraform_state: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-terraform-state]
module.platform.module.application.aws_ssm_parameter.transfer_token_salt: Refreshing state... [id=/forge/aws/cms/prod/TRANSFER_TOKEN_SALT]
module.platform.module.application.aws_ssm_parameter.preview_secret: Refreshing state... [id=/forge/aws/cms/prod/PREVIEW_SECRET]
module.platform.module.application.aws_ssm_parameter.api_token_salt: Refreshing state... [id=/forge/aws/cms/prod/API_TOKEN_SALT]
module.github.data.aws_kms_alias.cms_ssm_stage[0]: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-cms-stage-ssm]
module.platform.module.application.aws_ssm_parameter.strapi_internal_api_token: Refreshing state... [id=/forge/aws/cms/prod/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.application.aws_ssm_parameter.jwt_secret: Refreshing state... [id=/forge/aws/cms/prod/JWT_SECRET]
module.platform.module.application.aws_ssm_parameter.encryption_key: Refreshing state... [id=/forge/aws/cms/prod/ENCRYPTION_KEY]
module.platform.module.application.aws_ssm_parameter.admin_jwt_secret: Refreshing state... [id=/forge/aws/cms/prod/ADMIN_JWT_SECRET]
module.platform.module.application.aws_kms_alias.cms_ssm: Refreshing state... [id=alias/forge-cms-prod-ssm]
module.platform.module.application.aws_ssm_parameter.app_keys: Refreshing state... [id=/forge/aws/cms/prod/APP_KEYS]
module.vercel.aws_ssm_parameter.api_token[0]: Refreshing state... [id=/forge/vercel/api_token]
module.vercel.aws_kms_alias.vercel_ssm[0]: Refreshing state... [id=alias/forge-vercel-prod-ssm]
module.iam[0].module.groups.module.require_mfa.data.aws_iam_policy_document.require_mfa: Reading...
module.iam[0].module.groups.module.require_mfa.data.aws_iam_policy_document.require_mfa: Read complete after 0s [id=1456850862]
data.aws_s3_bucket.terraform_state: Reading...
module.iam[0].module.groups.module.admin_readonly.aws_iam_group_policy_attachment.admin_readonly: Refreshing state... [id=forge-admin-readonly-20260311020030367100000001]
module.github.data.aws_kms_key.vercel_ssm_for_web: Reading...
module.github.data.aws_kms_key.vercel_ssm_for_web: Read complete after 0s [id=37e33c00-2088-4cb5-85a4-7136e2b364b1]
module.iam[0].module.groups.module.dev_secrets.data.aws_iam_policy_document.dev_secrets: Reading...
module.iam[0].module.groups.module.dev_secrets.data.aws_iam_policy_document.dev_secrets: Read complete after 0s [id=1400751492]
module.iam[0].module.groups.module.billing.aws_iam_group_policy.billing: Refreshing state... [id=forge-billing:forge-billing]
module.github.aws_iam_policy.github_actions_terraform_apply[0]: Refreshing state... [id=arn:aws:iam::031374266475:policy/forge-github-actions-terraform-apply]
module.iam[0].module.groups.module.login_profile.data.aws_iam_policy_document.login_profile: Reading...
module.iam[0].module.groups.module.login_profile.data.aws_iam_policy_document.login_profile: Read complete after 0s [id=3718434498]
module.github.data.aws_kms_key.cms_ssm_prod[0]: Reading...
module.github.data.aws_kms_key.cms_ssm_prod[0]: Read complete after 0s [id=ce229066-4018-4d88-8843-4aa9d08e7e47]
module.github.data.aws_kms_key.web_ssm_stage[0]: Reading...
data.aws_s3_bucket.terraform_state: Read complete after 0s [id=forge-terraform-state-031374266475]
module.platform.aws_ses_domain_dkim.forge[0]: Refreshing state... [id=forge.jesusfilm.org]
module.platform.aws_ses_domain_mail_from.forge[0]: Refreshing state... [id=forge.jesusfilm.org]
module.github.data.aws_kms_key.web_ssm_stage[0]: Read complete after 0s [id=b1b36fcd-b8b0-4a04-9478-d095b77a0ee7]
module.github.data.aws_iam_policy_document.github_actions_cms_deploy: Reading...
module.github.data.aws_iam_policy_document.github_actions_cms_deploy: Read complete after 0s [id=958694039]
module.platform.aws_security_group.ecs_service: Refreshing state... [id=sg-060df6d60e5a60ea3]
module.platform.aws_security_group.alb: Refreshing state... [id=sg-03e6315b5f8558f5b]
module.platform.aws_subnet.public[1]: Refreshing state... [id=subnet-097840dc648bae953]
module.platform.aws_internet_gateway.platform: Refreshing state... [id=igw-0b5613d3e238c8997]
module.platform.aws_subnet.private[0]: Refreshing state... [id=subnet-0f53ac64bf2e3b394]
module.platform.aws_subnet.private[1]: Refreshing state... [id=subnet-05a263f448b51eb44]
module.platform.aws_subnet.public[0]: Refreshing state... [id=subnet-00c2e931133e9a9a3]
module.platform.module.application.aws_iam_role_policy_attachment.ecs_execution: Refreshing state... [id=forge-cms-prod-execution-role/arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy]
module.github.data.aws_iam_policy_document.github_actions_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_assume_role: Read complete after 0s [id=4244198687]
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_assume_role: Read complete after 0s [id=1657400500]
module.github.data.aws_iam_policy_document.github_actions_terraform_apply_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_apply_assume_role: Read complete after 0s [id=3016267585]
module.github.data.aws_iam_policy_document.github_actions_web_deploy_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_web_deploy_assume_role: Read complete after 0s [id=2290995573]
module.github.data.aws_kms_key.web_ssm_prod[0]: Reading...
module.github.data.aws_kms_key.terraform_state: Reading...
module.platform.module.assets.aws_s3_bucket_lifecycle_configuration.assets_access_logs: Refreshing state... [id=forge-cms-prod-assets-access-logs]
module.github.data.aws_kms_key.web_ssm_prod[0]: Read complete after 0s [id=afd5ddfa-d609-4451-9781-f86cd1b8b378]
module.platform.module.assets.aws_s3_bucket_public_access_block.assets_access_logs: Refreshing state... [id=forge-cms-prod-assets-access-logs]
module.github.data.aws_kms_key.terraform_state: Read complete after 0s [id=68220f21-2ca1-4846-9c2a-c8b6e268e63c]
module.platform.module.assets.aws_s3_bucket_server_side_encryption_configuration.assets_access_logs: Refreshing state... [id=forge-cms-prod-assets-access-logs]
module.github.data.aws_kms_key.cms_ssm_stage[0]: Reading...
module.github.data.aws_kms_key.cms_ssm_stage[0]: Read complete after 0s [id=5936d262-64d2-447d-8c57-2c8536b7c793]
module.iam[0].module.groups.module.require_mfa.aws_iam_policy.require_mfa: Refreshing state... [id=arn:aws:iam::031374266475:policy/forge-require-mfa]
module.github.data.aws_iam_policy_document.github_actions_web_deploy: Reading...
module.github.data.aws_iam_policy_document.github_actions_web_deploy: Read complete after 0s [id=3100061465]
module.iam[0].module.groups.module.dev_secrets.aws_iam_group_policy.dev_secrets: Refreshing state... [id=forge-dev-secrets:forge-dev-secrets]
module.platform.module.assets.aws_s3_bucket_public_access_block.assets: Refreshing state... [id=forge-cms-prod-assets]
module.platform.module.assets.aws_s3_bucket_server_side_encryption_configuration.assets: Refreshing state... [id=forge-cms-prod-assets]
module.platform.module.assets.data.aws_iam_policy_document.assets_access_logs: Reading...
module.platform.module.assets.data.aws_iam_policy_document.assets_access_logs: Read complete after 0s [id=1618467773]
module.platform.module.assets.aws_s3_bucket_cors_configuration.assets: Refreshing state... [id=forge-cms-prod-assets]
module.platform.module.assets.aws_s3_bucket_versioning.assets: Refreshing state... [id=forge-cms-prod-assets]
module.platform.module.assets.aws_s3_bucket_logging.assets: Refreshing state... [id=forge-cms-prod-assets]
module.iam[0].module.groups.module.login_profile.aws_iam_group_policy.login_profile: Refreshing state... [id=forge-iam-login-profile:forge-iam-login-profile]
module.platform.module.application.aws_lb_target_group.cms: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:targetgroup/forge-cms-prod-tg/f6258b44491c5cd4]
module.platform.aws_s3_bucket_versioning.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.platform.data.aws_iam_policy_document.alb_logs: Reading...
module.platform.data.aws_iam_policy_document.alb_logs: Read complete after 0s [id=2401369402]
module.platform.aws_s3_bucket_public_access_block.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.platform.aws_s3_bucket_lifecycle_configuration.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.platform.aws_s3_bucket_server_side_encryption_configuration.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.platform.aws_security_group.rds: Refreshing state... [id=sg-0124150b9d256b226]
module.platform.aws_route_table.public: Refreshing state... [id=rtb-0532fcf2c00112598]
module.github.aws_iam_role.github_actions_cms_deploy: Refreshing state... [id=forge-github-actions-cms-deploy-prod]
module.github.aws_iam_role.github_actions_terraform_plan: Refreshing state... [id=forge-github-actions-terraform-plan-prod]
module.github.aws_iam_role.github_actions_terraform_apply: Refreshing state... [id=forge-github-actions-terraform-apply-prod]
module.platform.aws_nat_gateway.platform: Refreshing state... [id=nat-0d016d28e5aab58a2]
module.platform.aws_lb.platform: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:loadbalancer/app/forge-platform-prod-alb/32e2a7795932386a]
module.platform.module.assets.aws_s3_bucket_policy.assets_access_logs: Refreshing state... [id=forge-cms-prod-assets-access-logs]
module.platform.aws_route53_record.ses_mail_from_spf[0]: Refreshing state... [id=Z078031724OHPW03IKVLO_mail.forge.jesusfilm.org_TXT]
module.platform.aws_route53_record.ses_dkim[1]: Refreshing state... [id=Z078031724OHPW03IKVLO_mw5z7krl2q7hd4razp6f4ayl4qwohrm5._domainkey.forge.jesusfilm.org_CNAME]
module.platform.aws_route53_record.ses_verification[0]: Refreshing state... [id=Z078031724OHPW03IKVLO__amazonses.forge.jesusfilm.org_TXT]
module.platform.aws_route53_record.ses_dkim[2]: Refreshing state... [id=Z078031724OHPW03IKVLO_qrchzeo4lk3vzfhtpjgyexmwcakpdcng._domainkey.forge.jesusfilm.org_CNAME]
module.platform.aws_route53_record.ses_dkim[0]: Refreshing state... [id=Z078031724OHPW03IKVLO_wc53teienh4ajwwfvuxkp5mucpsdrali._domainkey.forge.jesusfilm.org_CNAME]
module.platform.aws_route53_record.ses_mail_from_mx[0]: Refreshing state... [id=Z078031724OHPW03IKVLO_mail.forge.jesusfilm.org_MX]
module.platform.aws_s3_bucket_policy.alb_logs: Refreshing state... [id=forge-platform-prod-alb-logs]
module.platform.module.application.aws_vpc_security_group_ingress_rule.cms_from_alb: Refreshing state... [id=sgr-001c90b1521a7d334]
module.platform.module.application.aws_vpc_security_group_egress_rule.alb_to_cms: Refreshing state... [id=sgr-0d333a84f55e67028]
module.platform.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0235d4b16f74bbb86]
module.platform.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-072c541cb48d831ae]
module.platform.module.application.aws_db_subnet_group.cms: Refreshing state... [id=forge-cms-prod-db-subnets]
module.platform.aws_route_table.private: Refreshing state... [id=rtb-04444ffb223712e53]
module.github.aws_iam_role_policy_attachment.github_actions_terraform_apply: Refreshing state... [id=forge-github-actions-terraform-apply-prod/arn:aws:iam::031374266475:policy/forge-github-actions-terraform-apply]
module.github.aws_ssm_parameter.terraform_aws_role_apply_arn: Refreshing state... [id=/forge/github/terraform_aws_role_apply_prod_arn]
module.platform.module.application.data.aws_iam_policy_document.ecs_task: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["github_plan"]: Reading...
module.platform.module.application.data.aws_iam_policy_document.ecs_task: Read complete after 0s [id=184648198]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["github_plan"]: Read complete after 0s [id=2799898213]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["github_apply"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["vercel_plan"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["github_apply"]: Read complete after 0s [id=1056384456]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["vercel_apply"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["vercel_apply"]: Read complete after 0s [id=4202215815]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["vercel_apply"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack["vercel_plan"]: Read complete after 0s [id=3706722257]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["vercel_apply"]: Read complete after 0s [id=2944720027]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["vercel_plan"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["vercel_plan"]: Read complete after 0s [id=3112099037]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["github_apply"]: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["github_apply"]: Read complete after 0s [id=1597332144]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["github_plan"]: Reading...
module.github.aws_ssm_parameter.terraform_aws_role_plan_arn: Refreshing state... [id=/forge/github/terraform_aws_role_plan_prod_arn]
module.github.aws_iam_role_policy_attachment.github_actions_terraform_plan_readonly: Refreshing state... [id=forge-github-actions-terraform-plan-prod/arn:aws:iam::aws:policy/ReadOnlyAccess]
module.github.data.aws_iam_policy_document.github_actions_terraform_stack_assume["github_plan"]: Read complete after 0s [id=2311664580]
module.platform.module.application.aws_route53_record.alb_cert_validation["cms.forge.jesusfilm.org"]: Refreshing state... [id=Z078031724OHPW03IKVLO__4e8973315cfba989c3fb2410ab13b8d9.cms.forge.jesusfilm.org._CNAME]
module.platform.module.assets.aws_route53_record.assets_cert_validation["assets.forge.jesusfilm.org"]: Refreshing state... [id=Z078031724OHPW03IKVLO__5438fc20314ef99d95985128ac6c17b1.assets.forge.jesusfilm.org._CNAME]
module.github.aws_ssm_parameter.cms_deploy_role_arn: Refreshing state... [id=/forge/github/cms_deploy_role_arn_prod]
module.github.aws_iam_role_policy.github_actions_cms_deploy: Refreshing state... [id=forge-github-actions-cms-deploy-prod:cms-deploy]
module.platform.aws_lb_listener.http_redirect: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener/app/forge-platform-prod-alb/32e2a7795932386a/097d0c48c307920f]
module.platform.aws_ses_domain_identity_verification.forge[0]: Refreshing state... [id=forge.jesusfilm.org]
module.platform.module.application.aws_iam_role_policy.ecs_task: Refreshing state... [id=forge-cms-prod-task-role:forge-cms-prod-task-policy]
module.iam[0].module.groups.module.login_profile.aws_iam_group_policy_attachment.require_mfa: Refreshing state... [id=forge-iam-login-profile-20260312015445700300000001]
module.iam[0].module.groups.module.dev_secrets.aws_iam_group_policy_attachment.require_mfa: Refreshing state... [id=forge-dev-secrets-20260312015445710400000002]
module.iam[0].module.groups.module.admin_readonly.aws_iam_group_policy_attachment.require_mfa: Refreshing state... [id=forge-admin-readonly-20260312015445715500000003]
module.iam[0].module.groups.module.billing.aws_iam_group_policy_attachment.require_mfa: Refreshing state... [id=forge-billing-20260312015445736900000004]
module.github.aws_iam_role.github_actions_terraform_stack["vercel_apply"]: Refreshing state... [id=forge-github-actions-terraform-vercel-apply]
module.github.aws_iam_role.github_actions_terraform_stack["vercel_plan"]: Refreshing state... [id=forge-github-actions-terraform-vercel-plan]
module.github.aws_iam_role.github_actions_terraform_stack["github_apply"]: Refreshing state... [id=forge-github-actions-terraform-github-apply]
module.github.aws_iam_role.github_actions_terraform_stack["github_plan"]: Refreshing state... [id=forge-github-actions-terraform-github-plan]
module.platform.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-0693204bbb9be4eca]
module.platform.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-0706f67fd1ea051f9]
module.platform.module.application.aws_route53_record.alb_alias: Refreshing state... [id=Z078031724OHPW03IKVLO_cms.forge.jesusfilm.org_A]
module.platform.module.application.aws_acm_certificate_validation.alb: Refreshing state... [id=2026-03-05 09:00:41.575 +0000 UTC]
module.platform.module.assets.aws_acm_certificate_validation.assets: Refreshing state... [id=2026-03-05 09:00:43.975 +0000 UTC]
module.platform.module.application.aws_db_instance.cms: Refreshing state... [id=db-DB2WTGAYSHKO5PXR2A3GNESLSY]
module.platform.aws_wafv2_web_acl_logging_configuration.alb: Refreshing state... [id=arn:aws:wafv2:us-east-2:031374266475:regional/webacl/forge-platform-prod-waf/5d527a2d-be37-446e-b4ed-53dd11c94e8a]
module.platform.aws_wafv2_web_acl_association.alb: Refreshing state... [id=arn:aws:wafv2:us-east-2:031374266475:regional/webacl/forge-platform-prod-waf/5d527a2d-be37-446e-b4ed-53dd11c94e8a,arn:aws:elasticloadbalancing:us-east-2:031374266475:loadbalancer/app/forge-platform-prod-alb/32e2a7795932386a]
module.platform.module.assets.aws_cloudfront_distribution.assets: Refreshing state... [id=E19IGKDD5ZI6JV]
module.platform.aws_lb_listener.https: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener/app/forge-platform-prod-alb/32e2a7795932386a/7cad837f4c43d1d4]
module.iam[0].module.users.module.tataihono.data.aws_iam_group.login_profile: Reading...
module.iam[0].module.users.module.tataihono.data.aws_iam_group.billing: Reading...
module.iam[0].module.users.module.tataihono.data.aws_iam_group.admin_readonly: Reading...
module.iam[0].module.users.module.tataihono.aws_iam_user.user: Refreshing state... [id=tataihono.nikora@jesusfilm.org]
module.iam[0].module.users.module.dev_secrets.aws_iam_user.dev_secrets["ur-imazing"]: Refreshing state... [id=ur-imazing-dev-secrets]
module.iam[0].module.users.module.dev_secrets.aws_iam_user.dev_secrets["kneesal"]: Refreshing state... [id=kneesal-dev-secrets]
module.iam[0].module.users.module.tataihono.data.aws_iam_group.login_profile: Read complete after 0s [id=AGPAQOTQNKBVQFQGB7HM5]
module.iam[0].module.users.module.dev_secrets.aws_iam_user.dev_secrets["tataihono"]: Refreshing state... [id=tataihono-dev-secrets]
module.iam[0].module.users.module.tataihono.data.aws_iam_group.billing: Read complete after 0s [id=AGPAQOTQNKBVURSDNG7FR]
module.iam[0].module.users.module.dev_secrets.aws_iam_user.dev_secrets["up-tandem"]: Refreshing state... [id=up-tandem-dev-secrets]
module.github.aws_ssm_parameter.terraform_vercel_role_plan_arn[0]: Refreshing state... [id=/forge/github/terraform_vercel_role_plan_arn]
module.iam[0].module.users.module.tataihono.data.aws_iam_group.admin_readonly: Read complete after 0s [id=AGPAQOTQNKBVX7ZG7L3DH]
module.github.aws_ssm_parameter.terraform_vercel_role_apply_arn[0]: Refreshing state... [id=/forge/github/terraform_vercel_role_apply_arn]
module.github.aws_ssm_parameter.terraform_github_role_apply_arn[0]: Refreshing state... [id=/forge/github/terraform_github_role_apply_arn]
module.github.aws_ssm_parameter.terraform_github_role_plan_arn[0]: Refreshing state... [id=/forge/github/terraform_github_role_plan_arn]
module.github.aws_iam_role_policy.github_actions_terraform_stack["vercel_plan"]: Refreshing state... [id=forge-github-actions-terraform-vercel-plan:terraform-vercel_plan-access]
module.github.aws_iam_role_policy.github_actions_terraform_stack["github_apply"]: Refreshing state... [id=forge-github-actions-terraform-github-apply:terraform-github_apply-access]
module.github.aws_iam_role_policy.github_actions_terraform_stack["github_plan"]: Refreshing state... [id=forge-github-actions-terraform-github-plan:terraform-github_plan-access]
module.github.aws_iam_role_policy.github_actions_terraform_stack["vercel_apply"]: Refreshing state... [id=forge-github-actions-terraform-vercel-apply:terraform-vercel_apply-access]
module.iam[0].module.users.module.tataihono.aws_iam_user_group_membership.admin: Refreshing state... [id=terraform-2026031102003106690000000a]
module.iam[0].module.users.module.dev_secrets.aws_iam_user_group_membership.dev_secrets["tataihono"]: Refreshing state... [id=terraform-20260311020030984200000006]
module.iam[0].module.users.module.dev_secrets.aws_iam_user_group_membership.dev_secrets["up-tandem"]: Refreshing state... [id=terraform-20260311020031002400000009]
module.iam[0].module.users.module.dev_secrets.aws_iam_user_group_membership.dev_secrets["ur-imazing"]: Refreshing state... [id=terraform-20260311020030996200000007]
module.iam[0].module.users.module.dev_secrets.aws_iam_user_group_membership.dev_secrets["kneesal"]: Refreshing state... [id=terraform-20260311020030997100000008]
module.platform.module.assets.aws_route53_record.assets_alias: Refreshing state... [id=Z078031724OHPW03IKVLO_assets.forge.jesusfilm.org_A]
module.platform.module.assets.data.aws_iam_policy_document.assets_bucket: Reading...
module.platform.module.assets.data.aws_iam_policy_document.assets_bucket: Read complete after 0s [id=2853188073]
module.platform.module.assets.aws_s3_bucket_policy.assets: Refreshing state... [id=forge-cms-prod-assets]
module.platform.module.application.aws_lb_listener_rule.cms_host: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener-rule/app/forge-platform-prod-alb/32e2a7795932386a/7cad837f4c43d1d4/7a6a0551cd1a52d7]
module.platform.module.application.data.aws_iam_policy_document.ecs_execution_secrets: Reading...
module.platform.module.application.aws_ecs_task_definition.cms: Refreshing state... [id=forge-cms-prod-task]
module.platform.module.application.data.aws_iam_policy_document.ecs_execution_secrets: Read complete after 0s [id=2105038920]
module.platform.module.application.aws_iam_role_policy.ecs_execution_secrets: Refreshing state... [id=forge-cms-prod-execution-role:forge-cms-prod-execution-secrets]
module.platform.module.application.aws_ecs_service.cms: Refreshing state... [id=arn:aws:ecs:us-east-2:031374266475:service/forge-cms-prod/forge-cms-prod-service]
module.platform.module.application.aws_appautoscaling_target.cms[0]: Refreshing state... [id=service/forge-cms-prod/forge-cms-prod-service]
module.platform.module.application.aws_appautoscaling_policy.cms_cpu[0]: Refreshing state... [id=forge-cms-prod-cpu]
module.platform.module.web.aws_kms_key.web_ssm: Refreshing state... [id=afd5ddfa-d609-4451-9781-f86cd1b8b378]
module.platform.module.web.data.aws_ssm_parameter.cms_preview_secret: Reading...
module.platform.module.web.data.aws_ssm_parameter.cms_strapi_api_token: Reading...
module.platform.module.web.data.aws_ssm_parameter.cms_preview_secret: Read complete after 0s [id=/forge/aws/cms/prod/PREVIEW_SECRET]
module.platform.module.web.data.aws_ssm_parameter.cms_strapi_api_token: Read complete after 0s [id=/forge/aws/cms/prod/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.web.aws_kms_alias.web_ssm: Refreshing state... [id=alias/forge-web-prod-ssm]
module.platform.module.web.aws_ssm_parameter.strapi_preview_secret: Refreshing state... [id=/forge/aws/web/prod/STRAPI_PREVIEW_SECRET]
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_ssm_kms: Reading...
module.platform.module.web.aws_ssm_parameter.strapi_api_token: Refreshing state... [id=/forge/aws/web/prod/STRAPI_API_TOKEN]
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_ssm_kms: Read complete after 0s [id=1644481360]
module.github.aws_iam_role_policy.github_actions_terraform_plan_ssm_kms: Refreshing state... [id=forge-github-actions-terraform-plan-prod:terraform-plan-ssm-kms]
module.platform.module.application.ephemeral.random_password.preview_secret: Closing...
module.platform.module.application.ephemeral.random_password.app_key_3: Closing...
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Closing...
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Closing...
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Closing...
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.encryption_key: Closing...
module.platform.module.application.ephemeral.random_password.api_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.api_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_3: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.encryption_key: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.preview_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_1: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Closing...
module.platform.module.application.ephemeral.random_password.app_key_1: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Closing...
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Closing...
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_2: Closing...
module.platform.module.application.ephemeral.random_password.app_key_4: Closing...
module.platform.module.application.ephemeral.random_password.app_key_4: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_2: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Closing...
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Closing complete after 0s

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # module.github.aws_iam_role.github_actions_web_deploy will be created
  + resource "aws_iam_role" "github_actions_web_deploy" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRoleWithWebIdentity"
                      + Condition = {
                          + StringEquals = {
                              + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
                            }
                          + StringLike   = {
                              + "token.actions.githubusercontent.com:sub" = [
                                  + "repo:JesusFilm/forge:ref:refs/heads/main",
                                  + "repo:JesusFilm/forge:environment:web-prod",
                                ]
                            }
                        }
                      + Effect    = "Allow"
                      + Principal = {
                          + Federated = "arn:aws:iam::031374266475:oidc-provider/token.actions.githubusercontent.com"
                        }
                      + Sid       = "AllowGitHubActionsWebDeployAssumeRole"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "forge-github-actions-web-deploy-prod"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "Environment" = "prod"
          + "ManagedBy"   = "terraform"
          + "Service"     = "github-actions"
        }
      + tags_all              = {
          + "Environment" = "prod"
          + "ManagedBy"   = "terraform"
          + "Service"     = "github-actions"
        }
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # module.github.aws_iam_role_policy.github_actions_terraform_stack["github_apply"] will be updated in-place
  ~ resource "aws_iam_role_policy" "github_actions_terraform_stack" {
        id          = "forge-github-actions-terraform-github-apply:terraform-github_apply-access"
        name        = "terraform-github_apply-access"
      ~ policy      = jsonencode(
          ~ {
              ~ Statement = [
                    # (3 unchanged elements hidden)
                    {
                        Action   = [
                            "kms:GenerateDataKey",
                            "kms:Decrypt",
                        ]
                        Effect   = "Allow"
                        Resource = "arn:aws:kms:us-east-2:031374266475:key/68220f21-2ca1-4846-9c2a-c8b6e268e63c"
                        Sid      = "StateKmsAccess"
                    },
                  ~ {
                      ~ Resource = [
                          + "arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/web_project_id",
                          + "arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/org_id",
                            "arn:aws:ssm:us-east-2:031374266475:parameter/forge/github/*",
                            # (2 unchanged elements hidden)
                        ]
                        # (3 unchanged attributes hidden)
                    },
                    {
                        Action   = "kms:Decrypt"
                        Effect   = "Allow"
                        Resource = [
                            "arn:aws:kms:us-east-2:031374266475:key/ce229066-4018-4d88-8843-4aa9d08e7e47",
                            "arn:aws:kms:us-east-2:031374266475:key/b3aebd98-e778-445f-8e66-f23b48dae25c",
                            "arn:aws:kms:us-east-2:031374266475:key/5936d262-64d2-447d-8c57-2c8536b7c793",
                        ]
                        Sid      = "ScopedSsmParameterDecrypt"
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (2 unchanged attributes hidden)
    }

  # module.github.aws_iam_role_policy.github_actions_terraform_stack["github_plan"] will be updated in-place
  ~ resource "aws_iam_role_policy" "github_actions_terraform_stack" {
        id          = "forge-github-actions-terraform-github-plan:terraform-github_plan-access"
        name        = "terraform-github_plan-access"
      ~ policy      = jsonencode(
          ~ {
              ~ Statement = [
                    # (3 unchanged elements hidden)
                    {
                        Action   = "kms:Decrypt"
                        Effect   = "Allow"
                        Resource = "arn:aws:kms:us-east-2:031374266475:key/68220f21-2ca1-4846-9c2a-c8b6e268e63c"
                        Sid      = "StateKmsAccess"
                    },
                  ~ {
                      ~ Resource = [
                          + "arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/web_project_id",
                          + "arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/org_id",
                            "arn:aws:ssm:us-east-2:031374266475:parameter/forge/github/*",
                            # (2 unchanged elements hidden)
                        ]
                        # (3 unchanged attributes hidden)
                    },
                    {
                        Action   = "kms:Decrypt"
                        Effect   = "All

... (truncated)

@github-actions
Copy link
Copy Markdown

🟨 Terraform plan (aws/stage) — Changes detected

Changes: +5 ~0 -0
Time:

TZ Time
UTC 2026-03-13, 3:56:21 a.m.
NZ 2026-03-13, 4:56:21 p.m.
PT 2026-03-12, 8:56:21 p.m.
ET 2026-03-12, 11:56:21 p.m.

Run: https://github.com/JesusFilm/forge/actions/runs/23035459861

module.platform.module.application.ephemeral.random_password.dev_app_key_2: Opening...
module.platform.module.application.ephemeral.random_password.preview_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Opening...
module.platform.module.application.ephemeral.random_password.app_key_2: Opening...
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.app_key_3: Opening...
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.app_key_2: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.preview_secret: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.app_key_1: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Opening complete after 1s
module.platform.module.application.ephemeral.random_password.api_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Opening...
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Opening...
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Opening...
module.platform.module.application.ephemeral.random_password.app_key_3: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_1: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.api_token_salt: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Opening...
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Opening...
module.platform.module.application.ephemeral.random_password.encryption_key: Opening...
module.platform.module.application.ephemeral.random_password.app_key_4: Opening...
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.jwt_secret: Opening...
data.aws_caller_identity.current: Reading...
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Opening complete after 0s
module.github.data.aws_iam_policy_document.github_actions_terraform_apply: Reading...
data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.data.aws_caller_identity.current: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_apply: Read complete after 0s [id=1914455514]
module.platform.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.module.assets.aws_s3_bucket.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.application.aws_acm_certificate.alb: Refreshing state... [id=arn:aws:acm:us-east-2:031374266475:certificate/a62eb55a-e4cf-4375-adb1-a73b5232f28a]
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Opening complete after 0s
module.platform.module.assets.data.aws_caller_identity.current: Reading...
module.platform.module.assets.aws_kms_key.assets: Refreshing state... [id=ebf807b3-624c-4a0f-9e6f-4c08f0eaff3e]
module.platform.module.application.aws_iam_role.ecs_execution: Refreshing state... [id=forge-cms-stage-execution-role]
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Opening complete after 0s
module.platform.module.assets.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.module.assets.aws_cloudfront_origin_access_control.assets: Refreshing state... [id=E2LML7ZZ1C2D9R]
module.platform.aws_vpc.platform: Refreshing state... [id=vpc-0040ec9fef124ece4]
module.platform.module.application.ephemeral.random_password.app_key_4: Opening complete after 0s
module.platform.aws_eip.nat: Refreshing state... [id=eipalloc-0895205d6a27b7bee]
module.platform.module.application.ephemeral.random_password.jwt_secret: Opening complete after 0s
data.aws_dynamodb_table.terraform_state_lock: Reading...
module.platform.module.application.ephemeral.random_password.encryption_key: Opening complete after 0s
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Opening complete after 0s
module.github.data.aws_kms_alias.vercel_ssm_for_web: Reading...
module.github.data.aws_iam_policy.github_actions_terraform_apply[0]: Reading...
module.github.aws_iam_openid_connect_provider.github_actions: Refreshing state... [id=arn:aws:iam::031374266475:oidc-provider/token.actions.githubusercontent.com]
module.github.data.aws_kms_alias.vercel_ssm_for_web: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-vercel-prod-ssm]
module.platform.module.application.aws_iam_role.ecs_task: Refreshing state... [id=forge-cms-stage-task-role]
module.platform.module.application.aws_cloudwatch_log_group.cms: Refreshing state... [id=/ecs/forge-cms-stage]
module.platform.aws_cloudwatch_log_group.waf: Refreshing state... [id=aws-waf-logs-forge-platform-stage]
module.platform.aws_s3_bucket.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
data.aws_dynamodb_table.terraform_state_lock: Read complete after 0s [id=forge-terraform-locks]
module.github.data.aws_caller_identity.current: Reading...
module.github.data.aws_kms_alias.terraform_state: Reading...
module.github.data.aws_caller_identity.current: Read complete after 0s [id=031374266475]
module.platform.module.assets.aws_s3_bucket.assets_access_logs: Refreshing state... [id=forge-cms-stage-assets-access-logs]
module.platform.module.application.aws_ecs_cluster.cms: Refreshing state... [id=arn:aws:ecs:us-east-2:031374266475:cluster/forge-cms-stage]
module.github.data.aws_kms_alias.terraform_state: Read complete after 0s [id=arn:aws:kms:us-east-2:031374266475:alias/forge-terraform-state]
module.platform.module.application.aws_ecr_repository.cms: Refreshing state... [id=forge-cms-stage]
module.platform.module.application.aws_kms_key.cms_ssm: Refreshing state... [id=5936d262-64d2-447d-8c57-2c8536b7c793]
module.platform.data.aws_availability_zones.available: Reading...
data.aws_route53_zone.forge[0]: Reading...
module.platform.data.aws_availability_zones.available: Read complete after 0s [id=us-east-2]
data.aws_s3_bucket.terraform_state: Reading...
module.github.data.aws_kms_key.vercel_ssm_for_web: Reading...
module.platform.module.assets.aws_acm_certificate.assets: Refreshing state... [id=arn:aws:acm:us-east-1:031374266475:certificate/78e7de72-ab8b-49f4-a40a-02bb7c0d4f4e]
module.github.data.aws_kms_key.vercel_ssm_for_web: Read complete after 0s [id=37e33c00-2088-4cb5-85a4-7136e2b364b1]
module.platform.module.assets.aws_kms_alias.assets: Refreshing state... [id=alias/forge-cms-stage-assets]
module.github.data.aws_iam_policy_document.github_actions_terraform_apply_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_apply_assume_role: Read complete after 0s [id=4015899160]
module.github.data.aws_iam_policy_document.github_actions_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_assume_role: Read complete after 0s [id=754675411]
module.github.data.aws_iam_policy_document.github_actions_web_deploy_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_web_deploy_assume_role: Read complete after 0s [id=1922524263]
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_assume_role: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_assume_role: Read complete after 0s [id=309924271]
module.github.data.aws_iam_policy_document.github_actions_cms_deploy: Reading...
module.github.data.aws_iam_policy_document.github_actions_cms_deploy: Read complete after 0s [id=3739488026]
module.github.data.aws_kms_key.terraform_state: Reading...
module.github.data.aws_kms_key.terraform_state: Read complete after 0s [id=68220f21-2ca1-4846-9c2a-c8b6e268e63c]
module.platform.module.application.aws_iam_role_policy_attachment.ecs_execution: Refreshing state... [id=forge-cms-stage-execution-role/arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy]
data.aws_s3_bucket.terraform_state: Read complete after 1s [id=forge-terraform-state-031374266475]
module.platform.module.assets.aws_s3_bucket_versioning.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.assets.aws_s3_bucket_public_access_block.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.assets.aws_s3_bucket_server_side_encryption_configuration.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.assets.aws_s3_bucket_cors_configuration.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.application.aws_kms_alias.cms_ssm: Refreshing state... [id=alias/forge-cms-stage-ssm]
module.platform.module.application.aws_ssm_parameter.strapi_internal_api_token: Refreshing state... [id=/forge/aws/cms/stage/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.application.aws_ssm_parameter.app_keys: Refreshing state... [id=/forge/aws/cms/stage/APP_KEYS]
module.platform.module.application.aws_ssm_parameter.encryption_key: Refreshing state... [id=/forge/aws/cms/stage/ENCRYPTION_KEY]
module.platform.module.application.aws_ssm_parameter.dev_encryption_key[0]: Refreshing state... [id=/forge/aws/cms/dev/ENCRYPTION_KEY]
module.platform.module.application.aws_ssm_parameter.dev_strapi_internal_api_token[0]: Refreshing state... [id=/forge/aws/cms/dev/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.application.aws_ssm_parameter.dev_jwt_secret[0]: Refreshing state... [id=/forge/aws/cms/dev/JWT_SECRET]
module.platform.module.application.aws_ssm_parameter.api_token_salt: Refreshing state... [id=/forge/aws/cms/stage/API_TOKEN_SALT]
module.platform.module.application.aws_ssm_parameter.dev_transfer_token_salt[0]: Refreshing state... [id=/forge/aws/cms/dev/TRANSFER_TOKEN_SALT]
module.platform.module.application.aws_ssm_parameter.dev_app_keys[0]: Refreshing state... [id=/forge/aws/cms/dev/APP_KEYS]
module.platform.module.application.aws_ssm_parameter.transfer_token_salt: Refreshing state... [id=/forge/aws/cms/stage/TRANSFER_TOKEN_SALT]
module.platform.module.application.aws_ssm_parameter.admin_jwt_secret: Refreshing state... [id=/forge/aws/cms/stage/ADMIN_JWT_SECRET]
module.platform.module.application.aws_ssm_parameter.preview_secret: Refreshing state... [id=/forge/aws/cms/stage/PREVIEW_SECRET]
module.platform.module.application.aws_kms_alias.cms_ssm_dev[0]: Refreshing state... [id=alias/forge-cms-dev-ssm]
module.platform.module.application.aws_ssm_parameter.dev_preview_secret[0]: Refreshing state... [id=/forge/aws/cms/dev/PREVIEW_SECRET]
module.platform.module.application.aws_ssm_parameter.jwt_secret: Refreshing state... [id=/forge/aws/cms/stage/JWT_SECRET]
module.platform.module.application.aws_ssm_parameter.dev_api_token_salt[0]: Refreshing state... [id=/forge/aws/cms/dev/API_TOKEN_SALT]
module.platform.module.application.aws_ssm_parameter.dev_admin_jwt_secret[0]: Refreshing state... [id=/forge/aws/cms/dev/ADMIN_JWT_SECRET]
module.github.data.aws_iam_policy_document.github_actions_web_deploy: Reading...
module.github.data.aws_iam_policy_document.github_actions_web_deploy: Read complete after 0s [id=3100061465]
module.github.aws_iam_role.github_actions_terraform_apply: Refreshing state... [id=forge-github-actions-terraform-apply-stage]
module.github.data.aws_iam_policy.github_actions_terraform_apply[0]: Read complete after 1s [id=arn:aws:iam::031374266475:policy/forge-github-actions-terraform-apply]
module.github.aws_iam_role.github_actions_cms_deploy: Refreshing state... [id=forge-github-actions-cms-deploy-stage]
module.github.aws_iam_role.github_actions_terraform_plan: Refreshing state... [id=forge-github-actions-terraform-plan-stage]
module.platform.aws_s3_bucket_server_side_encryption_configuration.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
module.platform.data.aws_iam_policy_document.alb_logs: Reading...
module.platform.data.aws_iam_policy_document.alb_logs: Read complete after 0s [id=1858962669]
module.platform.aws_s3_bucket_public_access_block.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
module.platform.aws_s3_bucket_lifecycle_configuration.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
module.platform.aws_s3_bucket_versioning.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
module.platform.module.assets.aws_s3_bucket_logging.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.module.assets.aws_s3_bucket_lifecycle_configuration.assets_access_logs: Refreshing state... [id=forge-cms-stage-assets-access-logs]
module.platform.module.assets.data.aws_iam_policy_document.assets_access_logs: Reading...
module.platform.module.assets.data.aws_iam_policy_document.assets_access_logs: Read complete after 0s [id=323064003]
module.platform.module.assets.aws_s3_bucket_public_access_block.assets_access_logs: Refreshing state... [id=forge-cms-stage-assets-access-logs]
module.platform.module.assets.aws_s3_bucket_server_side_encryption_configuration.assets_access_logs: Refreshing state... [id=forge-cms-stage-assets-access-logs]
module.platform.aws_internet_gateway.platform: Refreshing state... [id=igw-0dca6115df12a416c]
module.platform.aws_security_group.ecs_service: Refreshing state... [id=sg-0c75adbe59945b2e2]
module.platform.aws_subnet.private[0]: Refreshing state... [id=subnet-08cb983444108cad4]
module.platform.aws_subnet.private[1]: Refreshing state... [id=subnet-08acf4d6a0cf91289]
data.aws_route53_zone.forge[0]: Read complete after 1s [id=Z078031724OHPW03IKVLO]
module.platform.aws_subnet.public[0]: Refreshing state... [id=subnet-0ae5cc72794571e47]
module.platform.aws_subnet.public[1]: Refreshing state... [id=subnet-0650937425d48dd8f]
module.platform.aws_security_group.alb: Refreshing state... [id=sg-0075aef920fa1e986]
module.platform.module.application.data.aws_iam_policy_document.ecs_task: Reading...
module.platform.module.application.data.aws_iam_policy_document.ecs_task: Read complete after 0s [id=2019633846]
module.platform.aws_s3_bucket_policy.alb_logs: Refreshing state... [id=forge-platform-stage-alb-logs]
module.platform.module.assets.aws_s3_bucket_policy.assets_access_logs: Refreshing state... [id=forge-cms-stage-assets-access-logs]
module.github.aws_ssm_parameter.terraform_aws_role_apply_arn: Refreshing state... [id=/forge/github/terraform_aws_role_apply_stage_arn]
module.platform.module.application.aws_lb_target_group.cms: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:targetgroup/forge-cms-stage-tg/73976e5b63c4370d]
module.platform.aws_wafv2_web_acl.alb: Refreshing state... [id=efb05d09-b544-4c38-995e-b646ec91dd29]
module.github.aws_ssm_parameter.cms_deploy_role_arn: Refreshing state... [id=/forge/github/cms_deploy_role_arn_stage]
module.github.aws_iam_role_policy.github_actions_cms_deploy: Refreshing state... [id=forge-github-actions-cms-deploy-stage:cms-deploy]
module.github.aws_iam_role_policy_attachment.github_actions_terraform_plan_readonly: Refreshing state... [id=forge-github-actions-terraform-plan-stage/arn:aws:iam::aws:policy/ReadOnlyAccess]
module.github.aws_ssm_parameter.terraform_aws_role_plan_arn: Refreshing state... [id=/forge/github/terraform_aws_role_plan_stage_arn]
module.platform.aws_route_table.public: Refreshing state... [id=rtb-02f919913a845c8a3]
module.platform.aws_security_group.rds: Refreshing state... [id=sg-0224a96f3740f4537]
module.github.aws_iam_role_policy_attachment.github_actions_terraform_apply: Refreshing state... [id=forge-github-actions-terraform-apply-stage/arn:aws:iam::031374266475:policy/forge-github-actions-terraform-apply]
module.platform.module.application.aws_iam_role_policy.ecs_task: Refreshing state... [id=forge-cms-stage-task-role:forge-cms-stage-task-policy]
module.platform.aws_lb.platform: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:loadbalancer/app/forge-platform-stage-alb/dc88600f279821b0]
module.platform.aws_nat_gateway.platform: Refreshing state... [id=nat-025e58577ccb794fa]
module.platform.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0a95ea0dfe84fc92b]
module.platform.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-05ab32ccd7e67f90f]
module.platform.module.application.aws_db_subnet_group.cms: Refreshing state... [id=forge-cms-stage-db-subnets]
module.platform.module.application.aws_vpc_security_group_egress_rule.alb_to_cms: Refreshing state... [id=sgr-06fda286a79c919bb]
module.platform.module.application.aws_vpc_security_group_ingress_rule.cms_from_alb: Refreshing state... [id=sgr-028258e5908101779]
module.platform.aws_route_table.private: Refreshing state... [id=rtb-090fd093fd497c232]
module.platform.module.assets.aws_route53_record.assets_cert_validation["assets.stage.forge.jesusfilm.org"]: Refreshing state... [id=Z078031724OHPW03IKVLO__119fbb353ee3dad6d705782901195439.assets.stage.forge.jesusfilm.org._CNAME]
module.platform.module.application.aws_route53_record.alb_cert_validation["cms.stage.forge.jesusfilm.org"]: Refreshing state... [id=Z078031724OHPW03IKVLO__8c049a04c1fcb9caa395b6ff6c2322ab.cms.stage.forge.jesusfilm.org._CNAME]
module.platform.aws_lb_listener.http_redirect: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener/app/forge-platform-stage-alb/dc88600f279821b0/9afbee052137b89b]
module.platform.module.application.aws_route53_record.alb_alias: Refreshing state... [id=Z078031724OHPW03IKVLO_cms.stage.forge.jesusfilm.org_A]
module.platform.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-03a23da7dfede2b5c]
module.platform.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-03f8ffd18d6d6b8e7]
module.platform.module.application.aws_db_instance.cms: Refreshing state... [id=db-IFW2HVA2RR22XVOXSTKMLAALH4]
module.platform.module.application.aws_acm_certificate_validation.alb: Refreshing state... [id=2026-03-05 08:15:50.293 +0000 UTC]
module.platform.module.application.data.aws_iam_policy_document.ecs_execution_secrets: Reading...
module.platform.module.application.aws_ecs_task_definition.cms: Refreshing state... [id=forge-cms-stage-task]
module.platform.module.application.data.aws_iam_policy_document.ecs_execution_secrets: Read complete after 0s [id=2752775232]
module.platform.module.application.aws_iam_role_policy.ecs_execution_secrets: Refreshing state... [id=forge-cms-stage-execution-role:forge-cms-stage-execution-secrets]
module.platform.aws_lb_listener.https: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener/app/forge-platform-stage-alb/dc88600f279821b0/96798e67efb942a4]
module.platform.module.application.aws_ecs_service.cms: Refreshing state... [id=arn:aws:ecs:us-east-2:031374266475:service/forge-cms-stage/forge-cms-stage-service]
module.platform.module.assets.aws_acm_certificate_validation.assets: Refreshing state... [id=2026-03-05 08:15:45.225 +0000 UTC]
module.platform.module.application.aws_lb_listener_rule.cms_host: Refreshing state... [id=arn:aws:elasticloadbalancing:us-east-2:031374266475:listener-rule/app/forge-platform-stage-alb/dc88600f279821b0/96798e67efb942a4/44dd4fd01dabc8c2]
module.platform.module.assets.aws_cloudfront_distribution.assets: Refreshing state... [id=EJWPGU8999NAE]
module.platform.module.web.aws_ssm_parameter.web_dev_next_public_graphql_url[0]: Refreshing state... [id=/forge/aws/web/dev/NEXT_PUBLIC_GRAPHQL_URL]
module.platform.module.web.data.aws_ssm_parameter.cms_strapi_api_token: Reading...
module.platform.module.web.aws_kms_key.web_ssm: Refreshing state... [id=b1b36fcd-b8b0-4a04-9478-d095b77a0ee7]
module.platform.module.web.data.aws_ssm_parameter.cms_preview_secret: Reading...
module.platform.module.web.data.aws_ssm_parameter.cms_ssm_dev_preview_secret[0]: Reading...
module.platform.module.web.data.aws_ssm_parameter.cms_ssm_dev_strapi_api_token[0]: Reading...
module.platform.module.web.data.aws_ssm_parameter.cms_strapi_api_token: Read complete after 0s [id=/forge/aws/cms/stage/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.web.data.aws_ssm_parameter.cms_preview_secret: Read complete after 0s [id=/forge/aws/cms/stage/PREVIEW_SECRET]
module.platform.module.web.data.aws_ssm_parameter.cms_ssm_dev_preview_secret[0]: Read complete after 0s [id=/forge/aws/cms/dev/PREVIEW_SECRET]
module.platform.module.web.data.aws_ssm_parameter.cms_ssm_dev_strapi_api_token[0]: Read complete after 0s [id=/forge/aws/cms/dev/STRAPI_INTERNAL_API_TOKEN]
module.platform.module.web.aws_kms_alias.web_ssm_dev[0]: Refreshing state... [id=alias/forge-web-dev-ssm]
module.platform.module.web.aws_ssm_parameter.strapi_preview_secret: Refreshing state... [id=/forge/aws/web/stage/STRAPI_PREVIEW_SECRET]
module.platform.module.web.aws_ssm_parameter.strapi_api_token: Refreshing state... [id=/forge/aws/web/stage/STRAPI_API_TOKEN]
module.platform.module.web.aws_ssm_parameter.web_dev_strapi_preview_secret[0]: Refreshing state... [id=/forge/aws/web/dev/STRAPI_PREVIEW_SECRET]
module.platform.module.web.aws_ssm_parameter.web_dev_strapi_api_token[0]: Refreshing state... [id=/forge/aws/web/dev/STRAPI_API_TOKEN]
module.platform.module.web.aws_kms_alias.web_ssm: Refreshing state... [id=alias/forge-web-stage-ssm]
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_ssm_kms: Reading...
module.github.data.aws_iam_policy_document.github_actions_terraform_plan_ssm_kms: Read complete after 0s [id=1963565985]
module.platform.module.assets.data.aws_iam_policy_document.assets_bucket: Reading...
module.platform.module.assets.data.aws_iam_policy_document.assets_bucket: Read complete after 0s [id=2742714030]
module.platform.module.assets.aws_route53_record.assets_alias: Refreshing state... [id=Z078031724OHPW03IKVLO_assets.stage.forge.jesusfilm.org_A]
module.github.aws_iam_role_policy.github_actions_terraform_plan_ssm_kms: Refreshing state... [id=forge-github-actions-terraform-plan-stage:terraform-plan-ssm-kms]
module.platform.module.assets.aws_s3_bucket_policy.assets: Refreshing state... [id=forge-cms-stage-assets]
module.platform.aws_wafv2_web_acl_association.alb: Refreshing state... [id=arn:aws:wafv2:us-east-2:031374266475:regional/webacl/forge-platform-stage-waf/efb05d09-b544-4c38-995e-b646ec91dd29,arn:aws:elasticloadbalancing:us-east-2:031374266475:loadbalancer/app/forge-platform-stage-alb/dc88600f279821b0]
module.platform.aws_wafv2_web_acl_logging_configuration.alb: Refreshing state... [id=arn:aws:wafv2:us-east-2:031374266475:regional/webacl/forge-platform-stage-waf/efb05d09-b544-4c38-995e-b646ec91dd29]
module.platform.module.application.ephemeral.random_password.app_key_3: Closing...
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Closing...
module.platform.module.application.ephemeral.random_password.api_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Closing...
module.platform.module.application.ephemeral.random_password.app_key_2: Closing...
module.platform.module.application.ephemeral.random_password.app_key_4: Closing...
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_3: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_2: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_4: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.admin_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_3: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_4: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_2: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_admin_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.api_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Closing...
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Closing...
module.platform.module.application.ephemeral.random_password.dev_encryption_key: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_strapi_internal_api_token: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Closing...
module.platform.module.application.ephemeral.random_password.dev_preview_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Closing...
module.platform.module.application.ephemeral.random_password.dev_app_key_1: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.transfer_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.app_key_1: Closing...
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.app_key_1: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_transfer_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Closing...
module.platform.module.application.ephemeral.random_password.dev_api_token_salt: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.encryption_key: Closing...
module.platform.module.application.ephemeral.random_password.encryption_key: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Closing...
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Closing...
module.platform.module.application.ephemeral.random_password.dev_jwt_secret: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.strapi_internal_api_token: Closing complete after 0s
module.platform.module.application.ephemeral.random_password.preview_secret: Closing...
module.platform.module.application.ephemeral.random_password.preview_secret: Closing complete after 0s

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.github.aws_iam_role.github_actions_web_deploy will be created
  + resource "aws_iam_role" "github_actions_web_deploy" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRoleWithWebIdentity"
                      + Condition = {
                          + StringEquals = {
                              + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
                            }
                          + StringLike   = {
                              + "token.actions.githubusercontent.com:sub" = [
                                  + "repo:JesusFilm/forge:ref:refs/heads/stage",
                                  + "repo:JesusFilm/forge:environment:web-stage",
                                  + "repo:JesusFilm/forge:environment:web-preview",
                                ]
                            }
                        }
                      + Effect    = "Allow"
                      + Principal = {
                          + Federated = "arn:aws:iam::031374266475:oidc-provider/token.actions.githubusercontent.com"
                        }
                      + Sid       = "AllowGitHubActionsWebDeployAssumeRole"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "forge-github-actions-web-deploy-stage"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "github-actions"
        }
      + tags_all              = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "github-actions"
        }
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # module.github.aws_iam_role_policy.github_actions_web_deploy will be created
  + resource "aws_iam_role_policy" "github_actions_web_deploy" {
      + id          = (known after apply)
      + name        = "web-deploy"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "ssm:GetParameters",
                          + "ssm:GetParameter",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ssm:us-east-2:031374266475:parameter/forge/vercel/api_token"
                      + Sid      = "SsmReadVercelToken"
                    },
                  + {
                      + Action   = "kms:Decrypt"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:kms:us-east-2:031374266475:key/37e33c00-2088-4cb5-85a4-7136e2b364b1"
                      + Sid      = "KmsDecryptVercelToken"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = (known after apply)
    }

  # module.github.aws_ssm_parameter.web_deploy_role_arn will be created
  + resource "aws_ssm_parameter" "web_deploy_role_arn" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/forge/github/web_deploy_role_arn_stage"
      + region         = "us-east-2"
      + tags_all       = (known after apply)
      + tier           = (known after apply)
      + type           = "String"
      + value          = (sensitive value)
      + value_wo       = (write-only attribute)
      + version        = (known after apply)
    }

  # module.platform.module.web.aws_ssm_parameter.next_public_cms_hostname will be created
  + resource "aws_ssm_parameter" "next_public_cms_hostname" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME"
      + region         = "us-east-2"
      + tags           = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "web"
        }
      + tags_all       = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "web"
        }
      + tier           = (known after apply)
      + type           = "String"
      + value          = (sensitive value)
      + value_wo       = (write-only attribute)
      + version        = (known after apply)
    }

  # module.platform.module.web.aws_ssm_parameter.next_public_graphql_url will be created
  + resource "aws_ssm_parameter" "next_public_graphql_url" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL"
      + region         = "us-east-2"
      + tags           = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "web"
        }
      + tags_all       = {
          + "Environment" = "stage"
          + "ManagedBy"   = "terraform"
          + "Service"     = "web"
        }
      + tier           = (known after apply)
      + type           = "String"
      + value          = (sensitive value)
      + value_wo       = (write-only attribute)
      + version        = (known after apply)
    }

Plan: 5 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
infra/github/environments.tf (1)

54-67: These environment blocks follow the established pattern but lack protection rules.

The web-preview, web-stage, and web-prod environments are created with the same minimal configuration as existing environments (aws-, cms-, vercel-, github-). However, the file comment indicates protection rules are in scope. Consider adding github_repository_environment_deployment_branch_policy resources alongside these definitions to enforce approval gates and branch protections for deployments—currently, this configuration is not managed in Terraform for any environment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/github/environments.tf` around lines 54 - 67, The three new
github_repository_environment resources
(github_repository_environment.web_preview, web_stage, web_prod) lack deployment
branch policies—add corresponding
github_repository_environment_deployment_branch_policy resources to each
environment to enforce approval gates and branch restrictions; for each policy
set repository = github_repository.forge.name, environment = the matching
github_repository_environment.*.environment, and configure protected_branches
(e.g., "main" for web_prod, "develop" or "release/*" for web_stage, and relevant
preview branches or patterns for web_preview) plus required reviewers/teams and
any bypass settings to match existing environment protections used elsewhere in
the repo so deployments are managed by Terraform.
infra/aws/github/web.tf (1)

1-3: Parameterize the Vercel KMS alias instead of hardcoding prod

Line [2] hardcodes alias/forge-vercel-prod-ssm, which makes this role definition environment-coupled. Prefer environment-driven alias input.

♻️ Suggested change
 data "aws_kms_alias" "vercel_ssm_for_web" {
-  name = "alias/forge-vercel-prod-ssm"
+  name = "alias/forge-vercel-${var.environment}-ssm"
 }

As per coding guidelines: "Keep Terraform modules explicit and environment inputs parameterized".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/aws/github/web.tf` around lines 1 - 3, The data resource
data.aws_kms_alias.vercel_ssm_for_web currently hardcodes name =
"alias/forge-vercel-prod-ssm"; replace that literal with a parameter (e.g.,
var.vercel_kms_alias or construct from var.environment) so the alias is
environment-driven. Add a variable declaration (variables.tf) for
vercel_kms_alias with a clear description (and optional default), update any
module/role callers to pass the correct alias (or environment) and adjust
documentation/README to show how to set the alias per environment. Ensure the
data resource uses the new variable (name = var.vercel_kms_alias or
interpolation) so the module is no longer prod-coupled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@infra/aws/github/terraform.tf`:
- Around line 382-392: The ssm_parameter_arns list in the resource defining IAM
permissions (the ssm_parameter_arns array shown in the diff) is missing the SSM
parameter ARNs for the Vercel organization and project IDs; add the two ARNs
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/org_id"
and
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/web_project_id"
to the ssm_parameter_arns lists (both places referenced in the diff) so the
Vercel plan/apply roles can read those parameters.

In `@infra/aws/vercel/ssm.tf`:
- Around line 46-68: The SSM parameters aws_ssm_parameter.org_id and
aws_ssm_parameter.web_project_id are set to a manual placeholder and use
lifecycle.ignore_changes which prevents Terraform-managed drift detection;
instead add Terraform input variables (e.g., var.vercel_org_id and
var.vercel_web_project_id), set the resources' value attributes to those
variables (value = var.vercel_org_id / value = var.vercel_web_project_id),
remove the lifecycle.ignore_changes entries, and keep the conditional creation
via local.create_vercel_ssm_resources unchanged so the values are fully managed
and auditable by Terraform.

In `@infra/github/data.tf`:
- Around line 61-75: The SSM data sources
(data.aws_ssm_parameter.web_deploy_role_arn_stage, web_deploy_role_arn_prod,
vercel_org_id, vercel_web_project_id) can fail during bootstrap; make the plan
bootstrap-safe by adding a boolean guard variable (e.g., var.bootstrap_mode or
var.fetch_ssm_parameters) and conditionally skip these reads with count =
var.bootstrap_mode ? 0 : 1 (or an equivalent feature-flag/count pattern) so the
data blocks are not evaluated when the producer stack / IAM is not yet present;
alternatively, if you prefer enforcing prerequisites, add explicit dependency
checks or a require-step that ensures the producer stack and IAM roles exist
before running plan (use depends_on or an upstream output check) and apply the
guard to each named data source above.

---

Nitpick comments:
In `@infra/aws/github/web.tf`:
- Around line 1-3: The data resource data.aws_kms_alias.vercel_ssm_for_web
currently hardcodes name = "alias/forge-vercel-prod-ssm"; replace that literal
with a parameter (e.g., var.vercel_kms_alias or construct from var.environment)
so the alias is environment-driven. Add a variable declaration (variables.tf)
for vercel_kms_alias with a clear description (and optional default), update any
module/role callers to pass the correct alias (or environment) and adjust
documentation/README to show how to set the alias per environment. Ensure the
data resource uses the new variable (name = var.vercel_kms_alias or
interpolation) so the module is no longer prod-coupled.

In `@infra/github/environments.tf`:
- Around line 54-67: The three new github_repository_environment resources
(github_repository_environment.web_preview, web_stage, web_prod) lack deployment
branch policies—add corresponding
github_repository_environment_deployment_branch_policy resources to each
environment to enforce approval gates and branch restrictions; for each policy
set repository = github_repository.forge.name, environment = the matching
github_repository_environment.*.environment, and configure protected_branches
(e.g., "main" for web_prod, "develop" or "release/*" for web_stage, and relevant
preview branches or patterns for web_preview) plus required reviewers/teams and
any bypass settings to match existing environment protections used elsewhere in
the repo so deployments are managed by Terraform.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23a5a2ee-c103-4e06-af8d-77e5eac8868a

📥 Commits

Reviewing files that changed from the base of the PR and between 148e1d7 and 8228064.

📒 Files selected for processing (14)
  • .github/workflows/web-deploy.yml
  • infra/aws/github/outputs.tf
  • infra/aws/github/ssm.tf
  • infra/aws/github/terraform.tf
  • infra/aws/github/web.tf
  • infra/aws/modules/platform/main.tf
  • infra/aws/modules/web/ssm_deploy.tf
  • infra/aws/modules/web/variables.tf
  • infra/aws/vercel/ssm.tf
  • infra/github/actions.tf
  • infra/github/data.tf
  • infra/github/environments.tf
  • infra/vercel/data.tf
  • infra/vercel/main.tf

Comment on lines 382 to 392
ssm_parameter_arns = [
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/api_token",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/cms/stage/STRAPI_INTERNAL_API_TOKEN",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/cms/prod/STRAPI_INTERNAL_API_TOKEN",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_API_TOKEN",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_API_TOKEN",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_PREVIEW_SECRET",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_PREVIEW_SECRET",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_GRAPHQL_URL",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME",
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_CMS_HOSTNAME",
]
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing SSM read scope for Vercel org/project IDs in Vercel stack roles

Line [383]-[392] and Line [425]-[434] don’t include /forge/vercel/org_id and /forge/vercel/web_project_id, but the Vercel stack reads them (seen in current plan failures). This leaves vercel_plan/vercel_apply unable to read required parameters.

🔧 Proposed fix
       ssm_parameter_arns = [
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/api_token",
+        "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/org_id",
+        "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/web_project_id",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_API_TOKEN",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_API_TOKEN",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_PREVIEW_SECRET",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_PREVIEW_SECRET",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_GRAPHQL_URL",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_CMS_HOSTNAME",
       ]
...
       ssm_parameter_arns = [
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/api_token",
+        "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/org_id",
+        "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/web_project_id",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_API_TOKEN",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_API_TOKEN",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/STRAPI_PREVIEW_SECRET",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/STRAPI_PREVIEW_SECRET",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_GRAPHQL_URL",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_GRAPHQL_URL",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/stage/NEXT_PUBLIC_CMS_HOSTNAME",
         "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/aws/web/prod/NEXT_PUBLIC_CMS_HOSTNAME",
       ]

Also applies to: 424-434

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/aws/github/terraform.tf` around lines 382 - 392, The ssm_parameter_arns
list in the resource defining IAM permissions (the ssm_parameter_arns array
shown in the diff) is missing the SSM parameter ARNs for the Vercel organization
and project IDs; add the two ARNs
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/org_id"
and
"arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/forge/vercel/web_project_id"
to the ssm_parameter_arns lists (both places referenced in the diff) so the
Vercel plan/apply roles can read those parameters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The infra/vercel stack does not read org_id or web_project_id from SSM — only infra/github does, and the github_plan/github_apply roles already include those ARNs (lines 460–461 and 498–499). This is a false positive; no change needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tataihono, you're absolutely right — I apologize for the false positive. Looking at the file, github_plan and github_apply already include /forge/vercel/org_id and /forge/vercel/web_project_id in their ssm_parameter_arns. The vercel_plan/vercel_apply roles only need access to the Vercel API token and the web SSM env vars that infra/vercel actually reads; org_id and web_project_id are consumed solely by infra/github. No change needed.


✏️ Learnings added
Learnt from: tataihono
Repo: JesusFilm/forge PR: 456
File: infra/aws/github/terraform.tf:382-392
Timestamp: 2026-03-13T11:34:16.837Z
Learning: In the JesusFilm/forge repository, the `infra/vercel` Terraform stack does NOT read `/forge/vercel/org_id` or `/forge/vercel/web_project_id` from AWS SSM. Only the `infra/github` stack reads those parameters (to populate GitHub Actions repository variables). The `vercel_plan` and `vercel_apply` IAM roles in `infra/aws/github/terraform.tf` therefore do not need ARNs for those parameters; the `github_plan` and `github_apply` roles already include them. Do not flag the absence of org_id/web_project_id ARNs from the vercel_plan/vercel_apply ssm_parameter_arns lists.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: tataihono
Repo: JesusFilm/forge PR: 302
File: infra/vercel/data.tf:7-15
Timestamp: 2026-03-09T03:06:44.765Z
Learning: In the JesusFilm/forge repository, `infra/vercel` is a single Terraform stack that intentionally manages both preview and production Vercel environment variables together. Both `data "aws_ssm_parameter" "strapi_api_token_stage"` and `data "aws_ssm_parameter" "strapi_api_token_prod"` are unconditional because the plan path must read both tokens to diff the complete desired state. Do not flag the prod token lookup as needing a production-only gate in this stack.

Learnt from: tataihono
Repo: JesusFilm/forge PR: 302
File: infra/aws/modules/cms/main.tf:154-161
Timestamp: 2026-03-09T03:06:52.258Z
Learning: In Terraform configurations under infra/aws (and similar Terraform files in the repo), using value_wo and value_wo_version on aws_ssm_parameter resources is valid for write-only secrets (Terraform 1.11+). These fields prevent secrets from being stored in the Terraform state and enable versioned secret rotation when used with ephemeral random_password resources. Do not flag these fields as invalid; they are intentional and part of secure secret management.

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: infra/AGENTS.md:0-0
Timestamp: 2026-02-15T22:26:08.219Z
Learning: Applies to infra/**/*.{tf,tfvars} : Terraform-only changes in infrastructure code

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: infra/AGENTS.md:0-0
Timestamp: 2026-02-15T22:26:08.219Z
Learning: Applies to infra/**/*.{tf,tfvars} : Keep Terraform modules explicit and environment inputs parameterized

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T02:00:52.599Z
Learning: Applies to infra/**/*.tf : Infrastructure changes must be implemented in Terraform at `infra/aws` and `infra/vercel`, not in application code.

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T02:00:52.599Z
Learning: Applies to infra/**/*.tf : Infra changes are Terraform-only.

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T03:58:30.823Z
Learning: Applies to infra/** : Use Terraform-only for infrastructure changes; never make manual console changes

Comment on lines +46 to +68
resource "aws_ssm_parameter" "org_id" {
count = local.create_vercel_ssm_resources ? 1 : 0

name = "/forge/vercel/org_id"
type = "String"
value = "manually set in AWS console"

lifecycle {
ignore_changes = [value]
}
}

resource "aws_ssm_parameter" "web_project_id" {
count = local.create_vercel_ssm_resources ? 1 : 0

name = "/forge/vercel/web_project_id"
type = "String"
value = "manually set in AWS console"

lifecycle {
ignore_changes = [value]
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid console-managed values for org_id/web_project_id; manage them via Terraform inputs.

Using manual console updates plus ignore_changes for these new parameters breaks drift detection and auditable infra-as-code flow.

♻️ Suggested Terraform-managed pattern
 resource "aws_ssm_parameter" "org_id" {
   count = local.create_vercel_ssm_resources ? 1 : 0

   name  = "/forge/vercel/org_id"
   type  = "String"
-  value = "manually set in AWS console"
-
-  lifecycle {
-    ignore_changes = [value]
-  }
+  value = var.vercel_org_id
 }

 resource "aws_ssm_parameter" "web_project_id" {
   count = local.create_vercel_ssm_resources ? 1 : 0

   name  = "/forge/vercel/web_project_id"
   type  = "String"
-  value = "manually set in AWS console"
-
-  lifecycle {
-    ignore_changes = [value]
-  }
+  value = var.vercel_web_project_id
 }
As per coding guidelines `infra/**: Use Terraform-only for infrastructure changes; never make manual console changes`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infra/aws/vercel/ssm.tf` around lines 46 - 68, The SSM parameters
aws_ssm_parameter.org_id and aws_ssm_parameter.web_project_id are set to a
manual placeholder and use lifecycle.ignore_changes which prevents
Terraform-managed drift detection; instead add Terraform input variables (e.g.,
var.vercel_org_id and var.vercel_web_project_id), set the resources' value
attributes to those variables (value = var.vercel_org_id / value =
var.vercel_web_project_id), remove the lifecycle.ignore_changes entries, and
keep the conditional creation via local.create_vercel_ssm_resources unchanged so
the values are fully managed and auditable by Terraform.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This follows the same bootstrap pattern as the existing api_token SSM parameter (line 33–44). All three values (api_token, org_id, web_project_id) are cross-stack references: they originate from the Vercel platform or the infra/vercel stack but need to be readable by the infra/github stack. Making org_id/web_project_id variable-managed while leaving api_token console-managed would be inconsistent.

A better follow-up would be to have infra/vercel write all three to SSM (it already has AWS provider access), eliminating the console-managed pattern entirely. Keeping as-is for now for consistency; will address in a dedicated follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tataihono, understood — the consistency argument is sound. Keeping all three bootstrap parameters (api_token, org_id, web_project_id) on the same console-managed + ignore_changes pattern avoids an asymmetric setup, and the proposed follow-up (having infra/vercel push all three into SSM directly) is the right long-term fix.

Would you like me to open a follow-up GitHub issue to track the work of having infra/vercel write all three values to SSM and eliminate the console-managed bootstrap pattern entirely?

( ._.)
<(' )> following the bootstrap pattern for now


✏️ Learnings added
Learnt from: tataihono
Repo: JesusFilm/forge PR: 456
File: infra/aws/vercel/ssm.tf:46-68
Timestamp: 2026-03-13T11:34:19.265Z
Learning: In infra/aws/vercel/ssm.tf (JesusFilm/forge), the aws_ssm_parameter resources for api_token, org_id, and web_project_id intentionally use a bootstrap pattern: value = "manually set in AWS console" with lifecycle { ignore_changes = [value] }. These are cross-stack references (Vercel platform / infra/vercel → infra/github) that cannot be populated by Terraform at creation time. Do not flag this pattern as a violation of the Terraform-only guideline; it is a known bootstrap approach. The intended follow-up is to have infra/vercel write all three values to SSM directly, eliminating the console-managed step.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: tataihono
Repo: JesusFilm/forge PR: 302
File: infra/vercel/data.tf:7-15
Timestamp: 2026-03-09T03:06:44.765Z
Learning: In the JesusFilm/forge repository, `infra/vercel` is a single Terraform stack that intentionally manages both preview and production Vercel environment variables together. Both `data "aws_ssm_parameter" "strapi_api_token_stage"` and `data "aws_ssm_parameter" "strapi_api_token_prod"` are unconditional because the plan path must read both tokens to diff the complete desired state. Do not flag the prod token lookup as needing a production-only gate in this stack.

Learnt from: tataihono
Repo: JesusFilm/forge PR: 302
File: infra/aws/modules/cms/main.tf:154-161
Timestamp: 2026-03-09T03:06:52.258Z
Learning: In Terraform configurations under infra/aws (and similar Terraform files in the repo), using value_wo and value_wo_version on aws_ssm_parameter resources is valid for write-only secrets (Terraform 1.11+). These fields prevent secrets from being stored in the Terraform state and enable versioned secret rotation when used with ephemeral random_password resources. Do not flag these fields as invalid; they are intentional and part of secure secret management.

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T02:00:52.599Z
Learning: Applies to infra/**/*.tf : Infrastructure changes must be implemented in Terraform at `infra/aws` and `infra/vercel`, not in application code.

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: infra/AGENTS.md:0-0
Timestamp: 2026-02-15T22:26:08.219Z
Learning: Applies to infra/**/*.{tf,tfvars} : Terraform-only changes in infrastructure code

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: infra/AGENTS.md:0-0
Timestamp: 2026-02-15T22:26:08.219Z
Learning: Applies to infra/**/*.{tf,tfvars} : Keep Terraform modules explicit and environment inputs parameterized

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T03:58:30.823Z
Learning: Applies to infra/** : Use Terraform-only for infrastructure changes; never make manual console changes

Learnt from: CR
Repo: JesusFilm/forge PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T02:00:52.599Z
Learning: Applies to infra/**/*.tf : Infra changes are Terraform-only.

Gate all web-deploy SSM data sources, environments, secrets, and
variables behind var.enable_web_deploy (default false) so the github
terraform-plan does not fail before the AWS and Vercel producer stacks
are applied.

Made-with: Cursor
@tataihono
Copy link
Copy Markdown
Contributor Author

Review feedback addressed (514e197)

Handled:

  • [github/data.tf — bootstrap failure (Critical)]: Added var.enable_web_deploy (default false) to gate all new SSM data sources, GitHub environments, secrets, and variables in the infra/github stack. The terraform-plan no longer fails when producer stacks haven't been applied yet.

Declined:

  • [terraform.tf — missing org_id/web_project_id in Vercel stack roles]: False positive. The infra/vercel stack does not read these SSM params — only infra/github does, and github_plan/github_apply roles already include those ARNs.
  • [vercel/ssm.tf — console-managed values]: Follows the same cross-stack bootstrap pattern as the existing api_token SSM parameter. Changing only org_id/web_project_id to variable-managed would be inconsistent. A better approach (having infra/vercel write to SSM directly) will be addressed in a dedicated follow-up.

@tataihono tataihono closed this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web-deploy): deploy web to vercel with previews and branch promotion

1 participant