Skip to content

feat: foundation scaffolding for org-wide config (Step 0 of 9)#2

Merged
JacobPEvans-personal merged 2 commits into
mainfrom
feat/foundation-scaffolding
May 30, 2026
Merged

feat: foundation scaffolding for org-wide config (Step 0 of 9)#2
JacobPEvans-personal merged 2 commits into
mainfrom
feat/foundation-scaffolding

Conversation

@JacobPEvans-personal
Copy link
Copy Markdown
Member

@JacobPEvans-personal JacobPEvans-personal commented May 30, 2026

Summary

Step 0 of a multi-step plan to make terraform-github the single source of truth for all org-level configuration — rulesets, actions settings, per-repo file content, labels. Foundation only: no new resources, no apply needed.

Updated 2026-05-30 to address review feedback (see commit 666f625):

  • The previous var.dot_github_repository_id with default 1220572589 and a description mentioning the org login was a magic number wrapped in another magic number. Replaced with data "github_repository" "dot_github" { name = ".github" } in a new data.tf. The org is implied by the provider's owner; the numeric ID is resolved at apply time.
  • templates/LICENSE.mit.tmpl removed. Canonical license text belongs to its upstream — Step 7's LICENSE enforcement fetches the MIT body via data "http" against https://api.github.com/licenses/mit at apply time, not from a committed copy.
  • Convention prose rewritten to be org-agnostic. The repo's .github is referenced by role ("the org's .github repo"), not by the literal dryvist/.github path. The providers.tf owner is the only place the org login appears.

What lands

  • No-magic-numbers convention seeded. No literal repo IDs, no org names in descriptions. The pattern is: data "<resource>" { … } in data.tf for identifiers GitHub already knows; variables.tf for true inputs; config/<name>.yml for thresholds and lists.
  • config/rulesets-defaults.yml — push-protection defaults (1 MB max file size, banned extensions .env .pem .key .p12 .pfx). Step 1's native org_push_protection ruleset consumes this via yamldecode(file(...)). The file-size enforcement is native (max_file_size push rule) — no custom workflow.
  • main.tf + outputs.tf — resolves the two pre-existing tflint terraform_standard_module_structure warnings. main.tf documents the file-split convention; outputs.tf is intentionally empty per the check's own warning text.
  • AGENTS.md + README.md — document the no-magic-numbers / org-agnostic-prose convention; describe data.tf and config/; note that canonical text (LICENSE, COC, etc.) is fetched at apply time. The markdown_lint_enforcement variable keeps its legacy evaluate default — flipping silently changes apply behavior — but new rulesets default to active.

Plan context (revised)

After this PR, the actual terraform apply-bearing steps. Step ordering changed since PR #9 in dryvist/.github was the wrong direction (custom file-size workflow) and got closed.

  1. org_push_protection ruleset — native max_file_size + file_extension_restriction push rules. Consumes config/rulesets-defaults.yml. Needs gh-claude-org-admin relaunch for apply.
  2. Branch protection + commit format rulesets.
  3. Org actions hardening (allowed actions allowlist, default workflow permissions, secret scanning org-wide).
  4. Per-repo declarative state via for_each: labels (canonical list from config/labels.yml), MIT LICENSE (body fetched from https://api.github.com/licenses/mit at apply time, no local template), CODEOWNERS.
  5. Cleanup of obsolete per-repo file-size cruft (workflow callers, .file-size.yml overrides, the now-redundant _file-size.yml in JacobPEvans-personal/.github).
  6. Community health files in dryvist/.github.
  7. JacobPEvans-personal/.github CLAUDE.md → AGENTS.md restructure (separate, not in this PR's chain).

Test plan

  • tofu init -backend=false && tofu validate — green (with the new data source)
  • pre-commit run --all-files (inside direnv exec .) — every hook passes, tflint clean
  • Both commits GPG-signed
  • Manual review of the rewritten AGENTS.md + README.md convention prose — org-agnostic enough?
  • No apply in this PR. The data source data.github_repository.dot_github will be resolved on the next live apply; on its own the PR is observably a no-op against the org.

Sets up the no-magic-numbers convention and the directory layout for the
planned expansion of terraform-github into the single source of truth for
all dryvist org-level configuration (rulesets, actions settings, per-repo
file content, labels). No new resources; no apply required.

Changes
-------
- variables.tf: add `dot_github_repository_id` (number, default
  1220572589, validated positive). Canonical example of the
  no-magic-numbers rule.
- rulesets.tf: drop the `locals { }` block; reference
  `var.dot_github_repository_id` instead of
  `local.dot_github_repository_id`.
- main.tf, outputs.tf: new. Resolves the two pre-existing tflint
  terraform_standard_module_structure warnings. main.tf documents the
  file-split convention (resources organized by topic in named files);
  outputs.tf is intentionally empty per the check's own warning text.
- config/rulesets-defaults.yml: new. Holds default thresholds (max file
  size, banned extensions) the upcoming `org_push_protection` ruleset
  will consume via yamldecode(file(...)).
- templates/LICENSE.mit.tmpl: new. MIT template for Step 7's per-repo
  `github_repository_file.license` for_each.
- AGENTS.md, README.md: document the no-magic-numbers convention, the
  config/ and templates/ directories, and the rule that new rulesets
  default to `active` enforcement. Existing `markdown_lint_enforcement`
  keeps its legacy `evaluate` default - changing it would silently flip
  enforcement on the next apply for any operator running `tofu apply`
  without overrides.

What this does NOT do
---------------------
- No new rulesets - those land in Step 1 (org_push_protection: native
  max_file_size + file_extension_restriction push rules) and beyond.
- No apply - only `tofu validate` was run. Apply requires ORG_ADMIN.
- terraform-github's own LICENSE is still Apache 2.0; Step 7 will
  reconcile it to MIT along with every other dryvist repo via
  github_repository_file.

Verification
------------
- tofu init -backend=false and tofu validate -> green
- pre-commit run --all-files -> all hooks pass

Assisted-by: Claude <noreply@anthropic.com>
…emplate; org-agnostic prose

PR feedback corrections:

- variables.tf: drop `dot_github_repository_id`. The literal default
  (1220572589) was a magic number; relocating it from a local to a
  variable did not solve that.
- data.tf: new. Adds `data "github_repository" "dot_github"` lookup
  that resolves the .github repo's numeric ID at apply time via the
  provider's already-configured `owner`. Reference as
  `data.github_repository.dot_github.repo_id`.
- rulesets.tf: switch `repository_id` from `var.dot_github_repository_id`
  to `data.github_repository.dot_github.repo_id`. Resource comment
  rewritten to reference the repo by role ("the org's `.github` repo")
  instead of by org-prefixed path.
- templates/LICENSE.mit.tmpl: removed. Canonical license text belongs
  to its upstream; Step 7's LICENSE enforcement fetches the MIT body
  via `data "http"` against https://api.github.com/licenses/mit at
  apply time, with `${year}` / `${holder}` substituted via `replace()`.
  No local copy to drift from the canonical source.
- AGENTS.md, README.md: prose now org-agnostic — references the repo
  by role ("the org's `.github` repo", "the org owner declared in the
  provider"). The `dryvist-only references` convention bullet is
  reworded to "no personal-account references" with explicit note that
  `providers.tf` `owner` is the single allowed mention of the org login.
  templates/ removed from the layout block. Mentions of `templatefile()`
  for bundled templates replaced by the data-http fetch pattern.

Verification
------------
- tofu init -backend=false and tofu validate -> green
- pre-commit run --all-files -> all hooks pass

Assisted-by: Claude <noreply@anthropic.com>
@JacobPEvans-personal JacobPEvans-personal merged commit 021d3ac into main May 30, 2026
3 checks passed
JacobPEvans-personal added a commit that referenced this pull request May 31, 2026
…ate, conventional commits (#4)

Codifies the pre-Terraform live state of the org-level rulesets and applies
the directives from #2/#3 review. Single PR because the imports must land
together with the new resources to avoid duplicate ruleset creation on
next apply.

New rulesets
------------
- `github_organization_ruleset.org_branch_protection` — non-bypassable
  quality gate on every default branch. Reverse-engineered from the
  pre-Terraform "main" org ruleset (id 15555419) and extended with strict
  Conventional Commits enforcement. Rules:
    - required_linear_history = true
    - required_signatures     = true
    - branch_name_pattern     = starts_with (main|develop|feat|fix|hotfix|release|chore)
    - commit_message_pattern  = regex enforcing Conventional Commits v1.0.0
    - pull_request            = thread resolution required, 0 approvers
                                (the approver requirement lives in
                                org_review_gate with admin bypass)
  No bypass_actors: signed commits, linear history, and Conventional
  Commits apply to every actor including OrganizationAdmins.

- `github_organization_ruleset.org_review_gate` — separate ruleset so the
  admin bypass below doesn't accidentally weaken the quality gates above.
  Rules:
    - pull_request { required_approving_review_count = 1,
                     require_code_owner_review = true,
                     required_review_thread_resolution = true,
                     allowed_merge_methods = [squash, rebase] }
  bypass_actors: OrganizationAdmin (actor_id=1, the API constant) in
  pull_request mode. Any OrganizationAdmin merges their own PRs without
  external review; non-admin actors must obtain the review.

  GitHub rulesets do not support per-author conditional rules. The closest
  approximation — "everyone but an admin needs a reviewer" — is
  implemented via OrganizationAdmin bypass. Granting a new account the
  OrganizationAdmin role extends this bypass to them.

Existing ruleset reconciled
---------------------------
- `github_organization_ruleset.markdown_lint` updated to match live state
  for clean import: ref_name = ~ALL (was ~DEFAULT_BRANCH),
  do_not_enforce_on_create = true. Default enforcement stays "evaluate"
  (legacy default); pass `-var markdown_lint_enforcement=active` to flip
  on. Live is currently "disabled" — apply moves it to "evaluate".

Import blocks
-------------
Declared in rulesets.tf so `tofu apply` adopts pre-Terraform state in the
same run that adds the new resources:

  import { to = github_organization_ruleset.org_branch_protection
           id = "15555419" }
  import { to = github_organization_ruleset.markdown_lint
           id = "17062292" }

org_review_gate is new (no existing live ruleset), so no import needed.

Config — no magic numbers
-------------------------
- config/rulesets-defaults.yml gains a `branch_protection` block with the
  branch name pattern, the Conventional Commits regex, and the allowed
  merge methods. Patterns are tunable here without editing .tf.
- locals.tf decodes the full config file once and exposes per-section
  locals (push_protection_defaults, branch_protection_defaults).

Conventions — identities out of source tree
-------------------------------------------
- AGENTS.md adds a "No identities anywhere except providers.tf owner"
  rule. No usernames, account logins, or person-tied identifiers in .tf
  resource bodies, comments, variable descriptions, config/*.yml, or
  CODEOWNERS-style files committed in this repo. CODEOWNERS for managed
  repos materializes at apply time from a Terraform variable.
- The `.github/CODEOWNERS` file that an earlier commit attempt added is
  intentionally NOT in this PR — it would have hardcoded an owner login.
  CODEOWNERS for this repo and all dryvist repos lands in a follow-up PR
  via `github_repository_file` with the owner identity as a `-var` input.

Conventions — cost policy
-------------------------
- AGENTS.md adds a "Cost policy" section documenting GitHub's pricing
  model and the rule "never apply a policy or enable a feature that
  costs money unless the PR body declares the cost and the operator
  approves it." Includes a matrix of free / GHAS-gated / metered /
  subscription features as of 2026-05, with sources, and a per-PR
  cost-impact checklist for any change that touches Actions, GHAS,
  Codespaces, Copilot, or org/repo settings affecting those.

Variables
---------
- New: org_branch_protection_enforcement (default "active")
- New: org_review_gate_enforcement (default "active")

Apply
-----
Requires the ORG_ADMIN token tier (gh-claude-org-admin). Apply will:
  1. Adopt rulesets 15555419 and 17062292 into Terraform state
  2. Rename the imported rulesets to org-branch-protection /
     org-markdown-lint
  3. Add commit_message_pattern rule to the imported branch-protection
     ruleset
  4. Create new org-review-gate ruleset
  5. Move markdown_lint enforcement from disabled -> evaluate

Cost impact
-----------
Free — all ruleset rules are native GitHub features at zero cost on any
plan. The markdown_lint workflow runs on Actions; public repos are free,
private repos count against the Team-plan free-tier minutes (3000/month
GitHub-hosted, $0.002/min self-hosted as of 2026-03-01). No GHAS feature
flipped on by this PR.

Verification
------------
- tofu init -backend=false and tofu validate -> green
- pre-commit run --all-files -> all hooks pass

Assisted-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant