Skip to content

Manage the fleet's GitHub settings with the Terraform GitHub provider - #82

Merged
cpitzi merged 1 commit into
mainfrom
terraform-fleet-settings
Aug 11, 2026
Merged

Manage the fleet's GitHub settings with the Terraform GitHub provider#82
cpitzi merged 1 commit into
mainfrom
terraform-fleet-settings

Conversation

@cpitzi

@cpitzi cpitzi commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Adds terraform/ — the Lentago Labs fleet's GitHub settings as code, via the
integrations/github provider — and adopts all 16 active org repos into it.

Until now fleet-ops/fleet-apply.sh enforced these settings imperatively: drift
existed until someone remembered to run a sweep, and the sweep's own output was
the only record of what changed. Terraform makes the fleet's configuration a
declared end state, so drift is a plan and a change is a reviewable diff. The
chosen scope is the full declarative surface including repository existence,
which makes creating a fleet repo an edit to a JSON file rather than a
click-through followed by a bootstrap sweep.

What Terraform now owns

Surface Resource Source of truth
Repo existence, description, homepage, visibility, features github_repository fleet-ops/repos.json (new)
Merge options, topic spine github_repository policy in terraform/locals.tf
main branch ruleset + required status checks github_repository_ruleset fleet-ops/required-checks.json
Tidewater issue-label palette github_issue_label fleet-ops/labels.json

The module reads the existing fleet-ops JSON rather than forking a second
copy of the fleet's settings. The only new manifest is repos.json, which holds
what no file previously did: per-repo identity and, now, membership.

Adoption is proven, not assumed

imports.tf adopts every live resource, and the acceptance test was a plan that
changes nothing it was not meant to. Run against the live fleet:

Plan: 189 to import, 0 to add, 25 to change, 0 to destroy.

189 imports — 16 repositories, 15 rulesets, 158 labels — all resolved. Of the 25
changes, 16 are the provider-only flags archive_on_destroy and
ignore_vulnerability_alerts_during_read materialising in state, which make no
API call. The remaining 1 repository and 9 labels are all myosotis: the one
repo fleet-apply.sh could never fully sweep, because it is private. It still
carries stock GitHub label colors, merge-commit and rebase-merge enabled, no
delete-branch-on-merge, and no spine topics. Every public repo plans clean.

That delta is the module's first apply and is a drift correction, not a policy
change. Nothing is applied by merging this PR — see Phasing below.

Blast-radius rails

Managing repo existence is the highest-risk thing in the fleet, so the guards
are layered, outermost first:

  1. The admin token is minted without delete_repo — a destroy cannot reach
    the API.
  2. archive_on_destroy = true — a destroyed resource archives the repo.
  3. prevent_destroy = true on every repository — Terraform refuses to plan a
    destroy, so deleting an entry from repos.json errors loudly instead of
    proposing to remove a live repo. Retirement is a deliberate archive +
    state rm, documented in terraform/README.md § Retiring a repo.
  4. Required checks gate the merge — in phase 2, below.

What stays in fleet-apply.sh

Two jobs have no declarative equivalent, and the second is load-bearing:

  • --prune-branches, an imperative sweep over live branch/PR state.
  • The required-context preflight. Requiring a check-run context whose
    workflow never reports deadlocks every PR on that repo, and Terraform will
    apply such a context without complaint. The script verifies a context has
    actually appeared on a recent PR, and remains the tool to run before editing
    required-checks.json.

Terraform does fix the other half of that history (#71): the script PUT a
wholesale replacement of the required-checks rule, so a live context missing
from the file vanished silently. A dropped context is now a line disappearing in
a diff, with the plan naming the removal before anyone merges it.

Phasing

Phase 1 (this PR) is operator-applied: CI runs fmt + validate only, and the
module has no credentials in Actions, so merging changes nothing on its own.
Phase 2 — the OIDC role, the admin token secret, plan-on-PR, apply-on-merge, and
requiring the gate context — is tracked in #81. The gate job ships here
already reporting on every PR so that flip is a one-line change.

Also in this PR

ci/validate.py gains a fleet check and extends configs to cover
repos.json. Together they assert the manifest's shape (a typo there is a
repository) and that repos.json, required-checks.json, labels.json and
brand/fleet.json agree on which repos exist — a public repo missing from
required-checks can't arm auto-merge, and one missing from brand ships without a
banner. Both were verified to fail on deliberately broken input before being
trusted, per this repo's rule that a check which cannot fail reads as coverage
without being it.

Follow-ups, deliberately not swept in

Three settings are seeded live-faithful so adoption plans clean; each is now
visible as data and is a one-line change rather than a migration. Four repos
carry model:haiku + model:opus but not model:sonnet;
suggest_branch_update is true on 7 of 16 repos with no visible pattern; and
squash_merge_commit_message = COMMIT_MESSAGES means the squash commit body is
the concatenated branch commits, not the PR body — which is not what the fleet
PR convention describes. Documented in terraform/README.md § Known
asymmetries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cpitzi
cpitzi enabled auto-merge (squash) August 11, 2026 05:32
@cpitzi
cpitzi merged commit ba663a7 into main Aug 11, 2026
7 checks passed
@cpitzi
cpitzi deleted the terraform-fleet-settings branch August 11, 2026 05:32
cpitzi added a commit that referenced this pull request Aug 14, 2026
* docs: reconstruct architecture decision records under docs/adr

Add six ADRs recovered on 2026-08-13 from repo history, PRs/issues,
CLAUDE.md, the incident register, and fleet session archives, plus the
docs/adr/README.md index and an "Architecture decisions" link in the
main README.

Each record's status date is the original decision date; every issue/PR
number, file path, and date was verified against this repo. Alternatives
sections separate the options weighed at the time from clearly-labelled
retrospective options, each honestly assessed.

- 0001 fleet-ops in the org .github meta-repo (PR #4, 2026-06-20)
- 0002 declarative Terraform settings-as-code (PR #82; #81 deferred)
- 0003 merge gate as push allowlist, not ruleset bypass (#96-#98)
- 0004 per-repo rulesets under the Free plan; org ruleset parked
- 0005 the incident register publishes verbatim (2026-07-13 owner call)
- 0006 required link checker from one shared resolver (#57/#66/#68)

Closes #108

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Apply pre-merge review fixes to reconstructed ADRs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: claude-runner[bot] <claude-runner[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Christopher Pitzi <cpitzi@gmail.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