Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,31 @@ tooling, not its Proxmox domain content.

## Conventions

- **dryvist-only references.** Every owner reference in this repo — provider
`owner`, `uses:`, renovate presets, remotes, links — is `dryvist`. Do not
introduce any personal-account owner references; this repo manages the
`dryvist` org and must point only at it.
- **No personal-account references.** Any owner reference that must exist —
`providers.tf` `owner`, `uses:`, renovate presets, remotes, links — points
at the org this repo manages, never at a personal account. (See also the
org-agnostic-code rule below: the org login appears in `providers.tf` and
documentation strings only, never in `.tf` resource bodies or variable
descriptions.)
- **No magic numbers in `.tf`. No specific identities in `.tf` either.**
Numeric or string values land in `variables.tf` (with type + validation +
description) or `config/<scope>.yml` (parsed via `yamldecode(file(...))`
into a local). For identifiers that GitHub already knows about — repo IDs,
the org's own login, account IDs — use a `data` source in `data.tf` and
reference the live value at apply time, never a literal default. The
canonical example is `data.github_repository.dot_github`: looks up the
org's `.github` repo by name (org is implied by the provider's `owner`),
exposes its numeric `repo_id` to org rulesets.
- **Code stays org-agnostic.** Don't bake the org's name into variable
descriptions, comments, or documentation strings. Write by role: "the
org's `.github` repo", "the org owner declared in the provider". The
`providers.tf` `owner = "dryvist"` is the single allowed mention of the
org login; everything else references roles.
- **`config/` holds non-`.tf` source data.** YAML thresholds, lists, and
any structured input the rulesets read at apply time. Canonical text the
org doesn't author (MIT LICENSE body, CODE_OF_CONDUCT, etc.) is fetched
from a trustworthy upstream via `data "http"` — never committed as a
local template.
- **No local markdownlint config.** This repo *defines* the org-wide
markdownlint ruleset (`github_organization_ruleset.markdown_lint`), whose
single source of truth is the workflow + `.markdownlint-cli2.yaml` in
Expand All @@ -38,11 +59,17 @@ Org ruleset changes require the **ORG_ADMIN** token tier
(`gh-claude-org-admin`) — the provider needs `admin:org`. The default `DRYVIST`
tier is read-only on org rulesets and will `403` on apply.

Roll out enforcement safely with the `evaluate` → `active` path: new org-wide
rules default to `evaluate` (dry-run, reports in Rulesets / Insights without
blocking merges). Confirm the fleet is green in Insights, then flip to `active`.
**New rulesets default to `active`.** Rules added going forward — push
protection, branch protection, commit format, etc. — set their
`<name>_enforcement` variable's default to `"active"` and apply enabled
directly. No dry-run gate. The variable still exists so a misbehaving rule
can be disabled with `-var <name>_enforcement=disabled` without a code
change.

**The existing `markdown_lint_enforcement` keeps its legacy `evaluate`
default** (changing it would silently flip enforcement on the next apply for
any operator who runs `tofu apply` without overrides). Enforce explicitly:

```bash
tofu apply # evaluate (default)
tofu apply -var markdown_lint_enforcement=active # enforce
tofu apply -var markdown_lint_enforcement=active
```
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,24 @@ policy, file-size limits, and repo settings can move here next.
## Layout

```text
versions.tf # terraform + provider pins, S3 backend (partial)
providers.tf # github provider (owner = dryvist), GITHUB_TOKEN auth
variables.tf # markdown_lint_enforcement (evaluate | active | disabled)
rulesets.tf # org rulesets
versions.tf # terraform + provider pins, S3 backend (partial)
providers.tf # github provider, GITHUB_TOKEN auth
variables.tf # all input variables (no magic numbers in .tf below)
data.tf # live lookups: repo IDs, org metadata — never literals
rulesets.tf # org rulesets (markdown_lint, …)
main.tf # multi-file entrypoint stub (resources organized by topic)
outputs.tf # intentionally empty — see file header
config/ # YAML thresholds + lists consumed via yamldecode(file(...))
```

`config/` holds plain-data thresholds, extension lists, label sets — read
into Terraform via `yamldecode(file(...))` and exposed as locals, never
inlined as `.tf` literals. `data.tf` holds live lookups (repo IDs, org
metadata, future repo enumerations) so no specific identity values are
baked into the code. Canonical text the org doesn't author — MIT LICENSE
body, CODE_OF_CONDUCT, etc. — is fetched at apply time from a trustworthy
upstream via `data "http"`, not committed as a local template.

## Requirements

- **OpenTofu** (>= 1.6) and the `integrations/github` provider, pinned in
Expand Down Expand Up @@ -64,11 +76,17 @@ tofu init -backend=false && tofu validate
```

**Rolling out a rule safely.** Org-wide enforcement can block merges everywhere
at once. Default the enforcement to `evaluate` (dry-run — reports in
**Rulesets / Insights** without blocking), confirm the fleet is green, then flip
to `active`:
at once. For `markdown_lint_enforcement` (legacy default `evaluate`), use the
dry-run gate before enforcing:

```bash
tofu apply # evaluate (default)
tofu apply # evaluate (legacy default)
tofu apply -var markdown_lint_enforcement=active # enforce
```

**New rulesets default to `active`.** The `evaluate` dry-run gate above is
specific to `markdown_lint_enforcement`'s legacy default. Rulesets added going
forward — push protection, branch protection, commit format, etc. — default
their `<name>_enforcement` variable to `"active"` and are applied enabled
directly. The variable still exists so a misbehaving rule can be disabled with
`-var <name>_enforcement=disabled` without a code change.
27 changes: 27 additions & 0 deletions config/rulesets-defaults.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Default thresholds and value sets for org-wide rulesets defined in
# rulesets.tf. Read with yamldecode(file(...)) and expose as locals. No
# literal numbers, lists, or thresholds in any .tf file — keep them here so
# they're tunable without editing Terraform code, and reviewable in a single
# place.

# Consumed by github_organization_ruleset.org_push_protection (added in a
# follow-up commit). Native GitHub push rules — enforced at the git layer
# without any workflow involvement.
push_protection:
# Hard ceiling on individual file size. Any push containing a single file
# larger than this is rejected by GitHub before it lands. Repos with a
# legitimate need for larger files get added to the bypass actor list
# rather than raising the org-wide ceiling.
max_file_size_mb: 1

# Banned file extensions. These extensions usually carry credentials or
# keys; pushes containing files with these extensions are rejected at the
# git layer. Repos that legitimately need to commit one of these (e.g. a
# test fixture) put them in a bypass-allowed path or add the actor to
# bypass.
banned_file_extensions:
- env
- pem
- key
- p12
- pfx
7 changes: 7 additions & 0 deletions data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Resolve the numeric repository ID of the org's `.github` repo at apply
# time, so org rulesets that reference workflows living in it never carry
# a literal ID. The provider's `owner` setting determines the org; this
# data source just names the repo.
data "github_repository" "dot_github" {
name = ".github"
}
12 changes: 12 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Multi-file root configuration. Resources are organized by topic in named
# .tf files rather than concentrated here:
#
# rulesets.tf — github_organization_ruleset.*
# (future) — org_settings.tf (github_actions_organization_*),
# repo_files.tf (github_repository_file.*),
# labels.tf (github_issue_labels.*)
#
# main.tf is the entrypoint required by tflint's standard-module-structure
# check. As top-level orchestration grows (e.g. a shared data lookup the
# topical files reference), it lands here. Until then, it's intentionally
# resource-free.
5 changes: 5 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# No outputs exposed at the root level. The side effects of `tofu apply` are
# the org-level rulesets, repo files, and settings created on GitHub
# directly — they're not consumable as Terraform outputs by callers. The
# standard-module-structure check requires this file to exist; per the
# check's own warning text, an empty outputs.tf is valid.
17 changes: 5 additions & 12 deletions rulesets.tf
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
locals {
# dryvist/.github holds the single source of truth for org CI: the reusable
# markdownlint workflow AND the canonical .markdownlint-cli2.yaml config.
# This numeric id is stable for the life of the repo (rename-safe).
dot_github_repository_id = 1220572589
}

# Org-wide markdown linting, enforced as a Required Workflow.
#
# Every repo's default-branch PRs must pass dryvist/.github's markdownlint
# workflow. The rule references ONE workflow + ONE config (both in
# dryvist/.github), so there are no per-repo markdownlint files to drift —
# this is the org-native replacement for per-repo `uses:` wiring.
# Every repo's default-branch PRs must pass the markdownlint workflow that
# lives in the org's `.github` repo (resolved at apply time via
# data.github_repository.dot_github). One workflow + one config — no
# per-repo markdownlint files to drift, no per-repo `uses:` wiring.
resource "github_organization_ruleset" "markdown_lint" {
name = "org-markdown-lint"
target = "branch"
Expand All @@ -30,7 +23,7 @@ resource "github_organization_ruleset" "markdown_lint" {
rules {
required_workflows {
required_workflow {
repository_id = local.dot_github_repository_id
repository_id = data.github_repository.dot_github.repo_id
path = ".github/workflows/markdownlint.yml"
ref = "refs/heads/main"
}
Expand Down
Loading