Skip to content

Sync from production #336

Sync from production

Sync from production #336

name: Sync from production
# Brings production's commits (release-please version bumps, community
# contributions) back onto staging `main` by fast-forwarding, keeping the two
# trunks identical and linear so the next `stlc build` reseals against the
# released state.
#
# Triggered by the production-side release dispatch, a periodic poll, and
# manual dispatch. Sealed into every two-repo staging SDK repo as custom code;
# the `if` guard routes so only the staging copy runs. Source of truth:
# dev-docs stainless/sdk-workflows/.
on:
schedule:
- cron: '17 */6 * * *'
workflow_dispatch: {}
repository_dispatch:
types: [prod-released]
permissions:
contents: read
concurrency:
group: stlc-sync-from-production
cancel-in-progress: true
jobs:
sync:
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/runner=2cpu-linux-x64/spot=false/tag=sdk-back-sync
if: >-
endsWith(github.repository, '-staging') &&
github.repository != 'orbcorp/orb-csharp-staging' &&
github.repository != 'orbcorp/orb-typescript-staging'
steps:
- name: Derive repo names
id: repos
run: |
short="${GITHUB_REPOSITORY#*/}"
echo "staging=$short" >> "$GITHUB_OUTPUT"
echo "production=${short%-staging}" >> "$GITHUB_OUTPUT"
- name: Mint app token
# The push to staging main authenticates as the app: the app is the
# ruleset bypass actor, and a required `trunk-synced` status check
# would otherwise deadlock this push (clearing the red is its job).
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.STLC_APP_ID }}
private-key: ${{ secrets.STLC_APP_PRIVATE_KEY }}
owner: orbcorp
repositories: ${{ steps.repos.outputs.staging }}
- name: Check out staging
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Fetch production main
env:
PRODUCTION_REPO: orbcorp/${{ steps.repos.outputs.production }}
run: |
# Production repos are public; reads need no credential.
git remote add production "https://github.com/${PRODUCTION_REPO}.git"
git fetch production main
- name: Check whether production has content staging lacks
id: diff
run: |
# Inverse of the promote guard: would merging production into
# staging change staging's tree? If not, staging already has
# production's content.
MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict
STAGING_TREE=$(git rev-parse 'origin/main^{tree}')
if [ "$MERGED" = "$STAGING_TREE" ]; then
echo "Staging already has production's content. Nothing to pull back."
echo "behind=false" >> "$GITHUB_OUTPUT"
else
echo "behind=true" >> "$GITHUB_OUTPUT"
fi
- name: Sync production to staging (fast-forward)
if: steps.diff.outputs.behind == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Refuse unless staging/main is an ancestor of production/main. If it
# is not, the trunks have forked (staging advanced out of band while
# a production change was unsynced) and a fast-forward is unsafe. The
# trunk-sync lock normally prevents this by freezing staging merges
# until the back-sync lands.
if ! git merge-base --is-ancestor origin/main production/main; then
echo "::error title=Back-sync blocked::staging main is not an ancestor of production/main."
exit 1
fi
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
production/main:refs/heads/main
echo "Fast-forwarded staging/main to production/main."