The applications object field is declared policies in variables.tf, but everything else uses the name ops_policies:
the cms tfvars entry: ops_policies = ["factory__cf_r2_arcodange_tf"]
the runbook: doc/runbooks/new-web-app/03-vault-platform.md:28,82
the guidebook: vibe/guidebooks/tools/secrets-and-vso.md:112
the module input: modules/app_policy variable ops_policies
Terraform silently drops unknown attributes when converting a value to an object() type, so cms's ops_policies was discarded and each.value.policies (main.tf:82) fell back to []. Net effect: gitea_cicd_cms never received the factory__cf_r2_arcodange_tf token policy (read on kvv1/cloudflare/r2/arcodange-tf + kvv1/zoho/self_client, defined in factory iac/cloudflare.tf:52). cms CI has been missing its Cloudflare R2 Terraform-state permissions.
The fix (root-cause, not symptom)
Rename the schema field policies → ops_policies + its single reference (main.tf:82each.value.policies → each.value.ops_policies). This aligns the whole chain.
I chose this over renaming the tfvars key (the obvious alternative) because:
it's lower churn — 2 lines, no tfvars change, no doc changes;
the runbook + guidebook already document ops_policies, so renaming the tfvars would also require fixing both docs to prevent the next app from copying the broken key again. Renaming the schema fixes it at the source.
gitea_cicd_cms gains factory__cf_r2_arcodange_tf in its token_policies. No other app sets this field (all default []), so no other role changes. The CI tofu apply must show onlyvault_jwt_auth_backend_role.gitea_jwt_cicd["cms"] updated in-place (token_policies gains the R2 policy) and 0 destroyed — I'll dispatch + confirm before merging.
Reviewer: please confirm factory__cf_r2_arcodange_tf is the intended grant for cms CI (it is per secrets-and-vso.md:112, but it's your prod permission so worth a glance).
## The bug
The `applications` object field is declared `policies` in `variables.tf`, but **everything else uses the name `ops_policies`**:
- the `cms` tfvars entry: `ops_policies = ["factory__cf_r2_arcodange_tf"]`
- the runbook: `doc/runbooks/new-web-app/03-vault-platform.md:28,82`
- the guidebook: `vibe/guidebooks/tools/secrets-and-vso.md:112`
- the module input: `modules/app_policy` variable `ops_policies`
Terraform **silently drops** unknown attributes when converting a value to an `object()` type, so cms's `ops_policies` was discarded and `each.value.policies` (`main.tf:82`) fell back to `[]`. Net effect: **`gitea_cicd_cms` never received the `factory__cf_r2_arcodange_tf` token policy** (read on `kvv1/cloudflare/r2/arcodange-tf` + `kvv1/zoho/self_client`, defined in factory `iac/cloudflare.tf:52`). cms CI has been missing its Cloudflare R2 Terraform-state permissions.
## The fix (root-cause, not symptom)
Rename the schema field `policies` → `ops_policies` + its single reference (`main.tf:82` `each.value.policies` → `each.value.ops_policies`). This aligns the whole chain.
I chose this over renaming the tfvars key (the obvious alternative) because:
- it's **lower churn** — 2 lines, no tfvars change, no doc changes;
- the runbook + guidebook **already** document `ops_policies`, so renaming the tfvars would *also* require fixing both docs to prevent the next app from copying the broken key again. Renaming the schema fixes it at the source.
```diff
- policies = optional(list(string), [])
+ ops_policies = optional(list(string), [])
...
- ops_policies = each.value.policies
+ ops_policies = each.value.ops_policies
```
## Behavioural change + merge gate
`gitea_cicd_cms` gains `factory__cf_r2_arcodange_tf` in its `token_policies`. No other app sets this field (all default `[]`), so no other role changes. The CI `tofu apply` must show **only** `vault_jwt_auth_backend_role.gitea_jwt_cicd["cms"]` updated in-place (token_policies gains the R2 policy) and **0 destroyed** — I'll dispatch + confirm before merging.
**Reviewer:** please confirm `factory__cf_r2_arcodange_tf` is the intended grant for cms CI (it is per `secrets-and-vso.md:112`, but it's your prod permission so worth a glance).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The `applications` object field was declared `policies` in variables.tf, but
the cms tfvars entry, the runbook (doc/runbooks/new-web-app/03-vault-platform.md),
the guidebook (vibe/guidebooks/tools/secrets-and-vso.md) and the module input
(modules/app_policy variable `ops_policies`) all use the name `ops_policies`.
Because Terraform silently drops unknown attributes when converting a value to
an object() type, cms's `ops_policies = ["factory__cf_r2_arcodange_tf"]` was
discarded and `each.value.policies` fell back to [] — so gitea_cicd_cms never
received the `factory__cf_r2_arcodange_tf` token policy (read on
kvv1/cloudflare/r2/arcodange-tf + kvv1/zoho/self_client, defined in
factory iac/cloudflare.tf). cms CI was missing its Cloudflare R2 Terraform-state
permissions.
Fix at the root: rename the schema field `policies` -> `ops_policies` (and its
single reference main.tf:82 `each.value.policies` -> `each.value.ops_policies`),
aligning the whole chain. This is lower-churn than renaming the tfvars key (the
chosen alternative would also have required fixing the runbook + guidebook, which
both already document `ops_policies`) and prevents the next app created from the
runbook from re-introducing the same silently-dropped key.
Behavioural change: gitea_cicd_cms gains `factory__cf_r2_arcodange_tf` in its
token_policies. No other app sets this field (all default []), so no other role
changes. Reviewer: confirm the R2 policy is the intended grant for cms CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The bug
The
applicationsobject field is declaredpoliciesinvariables.tf, but everything else uses the nameops_policies:cmstfvars entry:ops_policies = ["factory__cf_r2_arcodange_tf"]doc/runbooks/new-web-app/03-vault-platform.md:28,82vibe/guidebooks/tools/secrets-and-vso.md:112modules/app_policyvariableops_policiesTerraform silently drops unknown attributes when converting a value to an
object()type, so cms'sops_policieswas discarded andeach.value.policies(main.tf:82) fell back to[]. Net effect:gitea_cicd_cmsnever received thefactory__cf_r2_arcodange_tftoken policy (read onkvv1/cloudflare/r2/arcodange-tf+kvv1/zoho/self_client, defined in factoryiac/cloudflare.tf:52). cms CI has been missing its Cloudflare R2 Terraform-state permissions.The fix (root-cause, not symptom)
Rename the schema field
policies→ops_policies+ its single reference (main.tf:82each.value.policies→each.value.ops_policies). This aligns the whole chain.I chose this over renaming the tfvars key (the obvious alternative) because:
ops_policies, so renaming the tfvars would also require fixing both docs to prevent the next app from copying the broken key again. Renaming the schema fixes it at the source.Behavioural change + merge gate
gitea_cicd_cmsgainsfactory__cf_r2_arcodange_tfin itstoken_policies. No other app sets this field (all default[]), so no other role changes. The CItofu applymust show onlyvault_jwt_auth_backend_role.gitea_jwt_cicd["cms"]updated in-place (token_policies gains the R2 policy) and 0 destroyed — I'll dispatch + confirm before merging.Reviewer: please confirm
factory__cf_r2_arcodange_tfis the intended grant for cms CI (it is persecrets-and-vso.md:112, but it's your prod permission so worth a glance).🤖 Generated with Claude Code