Manage the fleet's GitHub settings with the Terraform GitHub provider - #82
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cpitzi
enabled auto-merge (squash)
August 11, 2026 05:32
This was referenced Aug 11, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
terraform/— the Lentago Labs fleet's GitHub settings as code, via theintegrations/githubprovider — and adopts all 16 active org repos into it.Until now
fleet-ops/fleet-apply.shenforced these settings imperatively: driftexisted 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
github_repositoryfleet-ops/repos.json(new)github_repositoryterraform/locals.tfmainbranch ruleset + required status checksgithub_repository_rulesetfleet-ops/required-checks.jsongithub_issue_labelfleet-ops/labels.jsonThe 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 holdswhat no file previously did: per-repo identity and, now, membership.
Adoption is proven, not assumed
imports.tfadopts every live resource, and the acceptance test was a plan thatchanges nothing it was not meant to. Run against the live fleet:
189 imports — 16 repositories, 15 rulesets, 158 labels — all resolved. Of the 25
changes, 16 are the provider-only flags
archive_on_destroyandignore_vulnerability_alerts_during_readmaterialising in state, which make noAPI call. The remaining 1 repository and 9 labels are all
myosotis: the onerepo
fleet-apply.shcould never fully sweep, because it is private. It stillcarries 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:
delete_repo— a destroy cannot reachthe API.
archive_on_destroy = true— a destroyed resource archives the repo.prevent_destroy = trueon every repository — Terraform refuses to plan adestroy, so deleting an entry from
repos.jsonerrors loudly instead ofproposing to remove a live repo. Retirement is a deliberate archive +
state rm, documented interraform/README.md§ Retiring a repo.What stays in
fleet-apply.shTwo jobs have no declarative equivalent, and the second is load-bearing:
--prune-branches, an imperative sweep over live branch/PR state.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+validateonly, and themodule 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
gatecontext — is tracked in #81. Thegatejob ships herealready reporting on every PR so that flip is a one-line change.
Also in this PR
ci/validate.pygains afleetcheck and extendsconfigsto coverrepos.json. Together they assert the manifest's shape (a typo there is arepository) and that
repos.json,required-checks.json,labels.jsonandbrand/fleet.jsonagree on which repos exist — a public repo missing fromrequired-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:opusbut notmodel:sonnet;suggest_branch_updateis true on 7 of 16 repos with no visible pattern; andsquash_merge_commit_message = COMMIT_MESSAGESmeans the squash commit body isthe concatenated branch commits, not the PR body — which is not what the fleet
PR convention describes. Documented in
terraform/README.md§ Knownasymmetries.