feat(infra): ./infra approve merges the promotion PR after the gate's own checks #154

Merged
jfraeys merged 2 commits from jfraeys/feat/infra-approve into main 2026-10-14 16:09:45 -04:00
Owner

Stacked on #152 (one front door): until #152 merges, this PR's diff also shows its commit. Merge #152 first.

./infra approve [PR] (scripts/approve-promotion.sh) replaces merging the promotion PR in the Forgejo UI, where merges silently did not take (#129, #133, #134; #116), and makes approval refuse anything the deploy gate would refuse later.

Checks, in order (each refusal says why; nothing is merged):

  1. the PR is main -> prod (the open one, or the number you give);
  2. staging validated a commit (passed on commit <sha> in the PR body);
  3. the PR's head is that commit, or descends from it with only files staging does not exercise (the gate's own PROMOTION_STAGED_RE, reused, not copied);
  4. CI on the head is green;
  5. scripts/plan-prod.sh both does not error (exit 2, changes or drift, is allowed but needs the stronger answer).

Then you type the PR number (<N> with changes when the plan found changes); it merges with tea and confirms on the API that the merge took, retrying briefly. Needs a terminal (refuses without one). Reads no secret. Prints the next step (git pull, ./infra deploy).

Tests: test-approve-promotion.sh, 15 cases with a fake tea serving fixture JSON, a fake plan with chosen exit codes and a throwaway git repo for the ancestry rule (approve, wrong answer, no PR, wrong number, unvalidated, docs-only drift accepted, roles drift refused, CI pending, plan error, plan changes needing the stronger answer, merge not visible, tea merge failure, usage, no terminal). Added to the lint workflow. test-infra-cli.sh +2 cases. Not run against the real Forgejo.

Docs: README, deployment.md and daily-commands.md point the approval step at ./infra approve.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ

**Stacked on #152** (one front door): until #152 merges, this PR's diff also shows its commit. Merge #152 first. `./infra approve [PR]` (`scripts/approve-promotion.sh`) replaces merging the promotion PR in the Forgejo UI, where merges silently did not take (#129, #133, #134; #116), and makes approval refuse anything the deploy gate would refuse later. **Checks, in order (each refusal says why; nothing is merged):** 1. the PR is `main -> prod` (the open one, or the number you give); 2. staging validated a commit (`passed on commit <sha>` in the PR body); 3. the PR's head is that commit, or descends from it with only files staging does not exercise (the gate's own `PROMOTION_STAGED_RE`, reused, not copied); 4. CI on the head is green; 5. `scripts/plan-prod.sh both` does not error (exit 2, changes or drift, is allowed but needs the stronger answer). Then you type the PR number (`<N> with changes` when the plan found changes); it merges with `tea` and confirms on the API that the merge took, retrying briefly. Needs a terminal (refuses without one). Reads no secret. Prints the next step (`git pull`, `./infra deploy`). **Tests:** `test-approve-promotion.sh`, 15 cases with a fake `tea` serving fixture JSON, a fake plan with chosen exit codes and a throwaway git repo for the ancestry rule (approve, wrong answer, no PR, wrong number, unvalidated, docs-only drift accepted, roles drift refused, CI pending, plan error, plan changes needing the stronger answer, merge not visible, tea merge failure, usage, no terminal). Added to the lint workflow. `test-infra-cli.sh` +2 cases. Not run against the real Forgejo. Docs: README, `deployment.md` and `daily-commands.md` point the approval step at `./infra approve`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
feat(infra): one front door: day-2 ops, every rotation, checks and keys through ./infra
All checks were successful
Lint / lint (pull_request) Successful in 1m52s
Secret scan / scan (pull_request) Successful in 26s
f10cac750d
./infra now also forwards health/backup/user/firewall/vpn/node/service/demo to
./manage (its global options work anywhere on the line), and covers the
credentials outside rotate-secret.sh's service list (rotate ovh-s3, ci-ssh-key,
headscale-api-key, ci-state-key), the read-only checks (check exposure,
versions) and SOPS age key creation (keys operator|recovery [project]).

./manage stays a separate, self-contained script: roles/manage_cli installs it on
the hosts as infra-manage, where git, tea and the vault are not available. Its
`secret` command now points at ./infra rotate. ./setup stays the engine; staging,
DR and bootstrap still call it directly.

daily-commands.md documents the commands and how the three entry points fit;
test-infra-cli.sh gains 13 dispatch cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
feat(infra): ./infra approve merges the promotion PR after the gate's own checks
Some checks failed
Lint / lint (pull_request) Has started running
Secret scan / scan (pull_request) Has been cancelled
61ae3878a9
scripts/approve-promotion.sh (./infra approve [PR]) checks, before anything is
merged, what the deploy gate will check later: the PR is main -> prod, staging
validated a commit, the PR's head is that commit or differs from it only in files
staging does not exercise (the gate's PROMOTION_STAGED_RE, reused), CI on the head
is green, and scripts/plan-prod.sh does not error. You then type the PR number
("<N> with changes" when the plan found changes); it merges with tea and confirms
on the API that the merge took. Replaces merging in the Forgejo UI, where merges
silently did not take (#129, #133, #134). Needs a terminal; reads no secret.

test-approve-promotion.sh: 15 cases with a fake tea, a fake plan and a throwaway
git repo for the ancestry rule; added to the lint workflow. Runbooks and README
point step 4 at ./infra approve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
jfraeys force-pushed jfraeys/feat/infra-approve from 61ae3878a9
Some checks failed
Lint / lint (pull_request) Has started running
Secret scan / scan (pull_request) Has been cancelled
to 6c2dd8a722
All checks were successful
Lint / lint (pull_request) Successful in 2m26s
Secret scan / scan (pull_request) Successful in 14s
2026-10-14 13:00:00 -04:00
Compare
jfraeys force-pushed jfraeys/feat/infra-approve from 6c2dd8a722
All checks were successful
Lint / lint (pull_request) Successful in 2m26s
Secret scan / scan (pull_request) Successful in 14s
to c9c79284a8
Some checks failed
Lint / lint (pull_request) Has been cancelled
Secret scan / scan (pull_request) Has been cancelled
2026-10-14 14:39:40 -04:00
Compare
jfraeys force-pushed jfraeys/feat/infra-approve from c9c79284a8
Some checks failed
Lint / lint (pull_request) Has been cancelled
Secret scan / scan (pull_request) Has been cancelled
to 276efcbf46
All checks were successful
Lint / lint (pull_request) Successful in 2m16s
Secret scan / scan (pull_request) Successful in 15s
2026-10-14 14:39:59 -04:00
Compare
Author
Owner

Rebased onto main (now includes #152 and #157). Added commit: promotion_drift in lib/setup/promotion.sh is now the one drift rule shared by the deploy gate (promotion_verify), plan-prod.sh and approve-promotion.sh. This fixes plan-prod's false [WARN] ... wait for the newer staging run when main moved past the validated commit only in files staging does not exercise (vault, docs, scripts outside lib/): no staging run ever comes for those, so the warning could never clear (seen 2026-10-14, 022a70a -> 54e508c). plan-prod now says OK in that case and names the files when it does warn. test-promotion +5 cases; checked on real history: 022a70a -> 54e508c = 0, 54e508c -> main = 1 listing the #152/#157 files.

Rebased onto main (now includes #152 and #157). **Added commit:** `promotion_drift` in `lib/setup/promotion.sh` is now the one drift rule shared by the deploy gate (`promotion_verify`), `plan-prod.sh` and `approve-promotion.sh`. This fixes plan-prod's false `[WARN] ... wait for the newer staging run` when main moved past the validated commit only in files staging does not exercise (vault, docs, scripts outside lib/): no staging run ever comes for those, so the warning could never clear (seen 2026-10-14, 022a70a -> 54e508c). plan-prod now says OK in that case and names the files when it does warn. test-promotion +5 cases; checked on real history: 022a70a -> 54e508c = 0, 54e508c -> main = 1 listing the #152/#157 files.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!154
No description provided.