From 80d0af0f5162f67a29e3b20d1a8ea0a05023e4ef Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Wed, 26 Aug 2026 23:14:47 -0600 Subject: [PATCH 1/2] feat(github): require PR checks on managed repositories --- gh-protections.tf | 24 +++++++++--------------- main.tf | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/gh-protections.tf b/gh-protections.tf index afb1df8..29885c1 100644 --- a/gh-protections.tf +++ b/gh-protections.tf @@ -1,30 +1,24 @@ -# Branch protection here is intentionally relaxed for a solo-maintainer, -# personal-dev organization: `contexts` is empty (CI is advisory, not a merge -# gate) and `required_approving_review_count` is zero. The PR workflow exists -# for CI validation, plan output, and change history — not review ceremony. -# If collaborators join or a repo gains external contributors, tighten -# `contexts` and `required_approving_review_count` at that time. +# Every active repository requires a pull request with its configured CI checks +# passing before merge. The check map lives in main.tf because check-run names +# differ by repository and GitHub treats an unknown required check as pending. resource "github_branch_protection" "protections" { for_each = toset([for repo in local.github_repositories : repo if !contains(local.archived_github_repositories, repo)]) repository_id = github_repository.repositories[each.key].node_id pattern = "main" - enforce_admins = false + enforce_admins = true allows_force_pushes = false - force_push_bypassers = ["${var.github_owner}/${github_team.admins.slug}"] required_linear_history = true require_conversation_resolution = true required_status_checks { strict = true - contexts = [] + contexts = local.required_status_checks_by_repository[each.key] } + # This block requires a pull request while retaining the solo-maintainer + # workflow: zero approvals, no code-owner gate, and no bypass actors. required_pull_request_reviews { - dismissal_restrictions = ["${var.github_owner}/${github_team.admins.slug}"] - dismiss_stale_reviews = true - pull_request_bypassers = ["${var.github_owner}/${github_team.admins.slug}"] - require_code_owner_reviews = true + require_code_owner_reviews = false required_approving_review_count = 0 - require_last_push_approval = true - restrict_dismissals = true + require_last_push_approval = false } restrict_pushes { push_allowances = [ diff --git a/main.tf b/main.tf index c372ec4..f7849b8 100644 --- a/main.tf +++ b/main.tf @@ -32,6 +32,24 @@ locals { for repo in local.github_repositories : repo if !contains(local.archived_github_repositories, repo) ]) + # Status-check names are GitHub check-run names, not workflow filenames. + # Keep this exhaustive for active repositories so a newly managed repository + # cannot silently receive a branch rule without a required CI check. + required_status_checks_by_repository = { + ".github" = ["pre-commit"] + "cflan" = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] + "images" = ["checks"] + "kustomize-cluster" = ["test"] + "opencode-server-config" = ["lint"] + "shared-workflows" = ["lint"] + "terraform-libvirt-domain" = ["test"] + "tfroot-aws" = ["opentofu / test", "opentofu / plan"] + "tfroot-cloudflare" = ["opentofu / test", "opentofu / plan"] + "tfroot-gcp" = ["opentofu / test", "opentofu / plan"] + "tfroot-github" = ["opentofu / test", "opentofu / plan"] + "tfroot-libvirt" = ["opentofu / test", "opentofu / plan"] + "www" = ["static-checks"] + } secrets = { "onion_s3_bucket" = { name = "ONION_AWS_S3_BUCKET" @@ -50,7 +68,7 @@ locals { } "onion_secret_access_key" = { name = "ONION_AWS_SECRET_ACCESS_KEY" - value = data.sops_file.secret_vars.data["onion_aws_secret_access_key"] + value = data.sops_file.secret_vars.data["onion_secret_access_key"] repositories = ["www"] } "www_s3_bucket" = { From d9a858a18e4ef57fb50f8b20218aad257038aca4 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Wed, 26 Aug 2026 23:15:41 -0600 Subject: [PATCH 2/2] fix(github): preserve onion secret mapping --- main.tf | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/main.tf b/main.tf index f7849b8..f59d8a9 100644 --- a/main.tf +++ b/main.tf @@ -36,19 +36,19 @@ locals { # Keep this exhaustive for active repositories so a newly managed repository # cannot silently receive a branch rule without a required CI check. required_status_checks_by_repository = { - ".github" = ["pre-commit"] - "cflan" = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] - "images" = ["checks"] - "kustomize-cluster" = ["test"] - "opencode-server-config" = ["lint"] - "shared-workflows" = ["lint"] + ".github" = ["pre-commit"] + "cflan" = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] + "images" = ["checks"] + "kustomize-cluster" = ["test"] + "opencode-server-config" = ["lint"] + "shared-workflows" = ["lint"] "terraform-libvirt-domain" = ["test"] - "tfroot-aws" = ["opentofu / test", "opentofu / plan"] - "tfroot-cloudflare" = ["opentofu / test", "opentofu / plan"] - "tfroot-gcp" = ["opentofu / test", "opentofu / plan"] - "tfroot-github" = ["opentofu / test", "opentofu / plan"] - "tfroot-libvirt" = ["opentofu / test", "opentofu / plan"] - "www" = ["static-checks"] + "tfroot-aws" = ["opentofu / test", "opentofu / plan"] + "tfroot-cloudflare" = ["opentofu / test", "opentofu / plan"] + "tfroot-gcp" = ["opentofu / test", "opentofu / plan"] + "tfroot-github" = ["opentofu / test", "opentofu / plan"] + "tfroot-libvirt" = ["opentofu / test", "opentofu / plan"] + "www" = ["static-checks"] } secrets = { "onion_s3_bucket" = { @@ -68,7 +68,7 @@ locals { } "onion_secret_access_key" = { name = "ONION_AWS_SECRET_ACCESS_KEY" - value = data.sops_file.secret_vars.data["onion_secret_access_key"] + value = data.sops_file.secret_vars.data["onion_aws_secret_access_key"] repositories = ["www"] } "www_s3_bucket" = {