ci: re-home prod promotion into a gated promote-to-main workflow (PR1: managed-LGC migration) (#63)

* ci: re-home prod promotion into a gated promote-to-main workflow

Migrate the prod-deploy gate to managed LangGraph Cloud (git-connected to
`main`) + Vercel. Under managed, a push to `main` auto-deploys prod, so the
dev -> main fast-forward IS the prod deploy trigger -- the bespoke AWS CD step
is obsolete and already gone from this workflow.

Re-home `promote-dev-to-prod.yml` -> `promote-to-main.yml`:
- gate the promote job on the `prod` GitHub Environment (required reviewer
  amoussa1229), restoring the manual prod-approval that the retired AWS CD job
  used to carry;
- drop the nightly auto-promote cron -- a scheduled auto-promotion conflicts
  with a manual approval gate now that the push deploys prod; promotion is
  workflow_dispatch only;
- keep the dev-HEAD-fully-green precondition and the seahaven-promotion App
  fast-forward push (sole non-admin bypass actor on `main` ruleset 18238334).

Update the companion check-dev-green.sh filename reference.

* ci: refuse promote-to-main dispatch from any ref other than dev

Defense-in-depth atop the already-pinned `ref: dev` checkout: workflow_dispatch
runs the workflow definition from the launched ref, so reject a non-dev dispatch
before the App token is minted. Surfaced by the GPT-4.1 cross-review of #63.
This commit is contained in:
Adam Moussa 2026-06-29 19:49:21 -04:00 • committed by GitHub
parent a4ed19ba61
commit 430a1cdff9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 8 deletions

View file

@ -4,7 +4,7 @@
# Reads check-runs on stdin — one
# name<US>status<US>conclusion<US>details_url
# per line, fields separated by ASCII Unit Separator (0x1F) — so it is unit-testable
# WITHOUT GitHub. promote-dev-to-prod.yml pipes the live `gh api .../check-runs`
# WITHOUT GitHub. promote-to-main.yml pipes the live `gh api .../check-runs`
# output in. 0x1F (not TAB) is used deliberately: TAB is IFS-whitespace, so an empty
# conclusion (every in_progress check has a null conclusion) would collapse and shift
# the columns — which would make the promote run fail to exclude itself. 0x1F is

View file

@ -1,11 +1,21 @@
name: Promote dev to main (prod)
# Gated dev -> main promotion (the PROD deploy gate under managed LangGraph Cloud).
#
# PROD now runs on managed LangGraph Cloud (git-connected to `main`) + Vercel (UI).
# The platform AUTO-DEPLOYS prod on every push to `main`, so a fast-forward of `main`
# to `dev` IS the prod deploy trigger -- there is no separate AWS deploy step anymore
# (the bespoke S3/SSM/packer CD is retired). This workflow therefore carries the whole
# prod-promotion gate:
# 1. manual dispatch only (no scheduled auto-promote -- prod ships when a human asks),
# 2. the `prod` GitHub Environment approval (required reviewer: amoussa1229),
# 3. the dev-HEAD-fully-green precondition (check-dev-green.sh),
# 4. a fast-forward-only push of `main` -> `dev` via the seahaven-promotion App, the
# sole non-admin bypass actor on the `main` ruleset (id 18238334).
name: Promote to main (prod)
permissions:
contents: write
on:
schedule:
- cron: "0 8 * * *"
workflow_dispatch:
concurrency:
@ -15,10 +25,29 @@ concurrency:
jobs:
promote:
runs-on: ubuntu-latest
# The manual-approval gate. The `prod` Environment's required reviewer
# (amoussa1229) must approve before this job runs -- and because the FF push
# below auto-deploys managed prod, that approval IS the prod-deploy approval.
environment: prod
permissions:
contents: write
checks: read
steps:
# Defense-in-depth: the checkout below pins `ref: dev`, so this workflow only
# ever promotes dev's HEAD. But workflow_dispatch runs the workflow DEFINITION
# from whichever ref it was launched on, so a branch that edited this file could
# otherwise reach the privileged steps. Refuse any dispatch not from `dev`,
# before the App token is minted. (The `prod` Environment approval still gates
# everything after this regardless.)
- name: Guard — only promote from dev
env:
DISPATCH_REF: ${{ github.ref_name }}
run: |
if [ "${DISPATCH_REF}" != "dev" ]; then
echo "::error::promote-to-main must be dispatched from 'dev' (got '${DISPATCH_REF}')."
exit 1
fi
echo "Dispatch ref OK: ${DISPATCH_REF}"
# Mint a GitHub App installation token for the protected-branch push below.
# A plain ref push by github-actions[bot] is REJECTED by the `main` ruleset
# (PRs required + a required status check; the default token is not a bypass
@ -56,10 +85,11 @@ jobs:
-q '.check_runs[] | [.name, .status, (.conclusion // ""), (.details_url // "")] | join("\u001f")' \
| bash .github/scripts/check-dev-green.sh
- name: Fast-forward main (PROD) to dev
# main is the production branch. A direct ref push is normally rejected by the
# `main` ruleset (PRs required), so this succeeds only because the App minted
# above is a bypass actor. The push is fast-forward-only, so a diverged main
# fails loudly rather than force-updating.
# main is the production branch: managed LangGraph Cloud is git-connected to it
# and auto-deploys prod on every push, so THIS push is the prod deploy trigger.
# A direct ref push is normally rejected by the `main` ruleset (PRs required),
# so it succeeds only because the App minted above is a bypass actor. The push
# is fast-forward-only, so a diverged main fails loudly rather than force-updating.
#
# SECURITY: this is the LAST step on purpose. The App token persists as the
# git credential after checkout — do not add steps after this push that run