Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 74 additions & 25 deletions .github/workflows/triggered-integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ env:
# docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/praxis-proxy/grid-ai-rollup:<tag>
GATEWAY_IMAGE_REF: ghcr.io/praxis-proxy/grid-ai-rollup:v0.1.3
GATEWAY_IMAGE_DIGEST: sha256:e5bdea9071f76533ab61714eeef6067c7449d0e9d811c221aa940cf3cdf9719f
# Pinned identity/date/message-suffix for the local PR-merge reconstruction
# in resolve-source's "Resolve source" step and glb-e2e's "Reconstruct PR
# merge" step (see grid#66). Both jobs build a merge commit from the same
# base_sha/head_sha pair and must produce a byte-identical SHA, which
# requires byte-identical author/committer identity, date, and message --
# a fixed, meaningless-but-consistent value is fine since this commit is a
# CI-internal integrity check, never published or citable.
MERGE_COMMIT_NAME: grid-ci-merge-check
MERGE_COMMIT_EMAIL: grid-ci-merge-check@users.noreply.github.com
MERGE_COMMIT_DATE: "1970-01-01T00:00:00Z"
MERGE_COMMIT_MESSAGE_SUFFIX: grid-ci deterministic merge check

jobs:
# ============================================================================
Expand All @@ -57,6 +68,7 @@ jobs:
source_kind: ${{ steps.source.outputs.source_kind }}
source_label: ${{ steps.source.outputs.source_label }}
tested_sha: ${{ steps.source.outputs.tested_sha }}
checkout_sha: ${{ steps.source.outputs.checkout_sha }}
pr_number: ${{ steps.pr.outputs.pr_number }}
title: ${{ steps.pr.outputs.title }}
url: ${{ steps.pr.outputs.url }}
Expand Down Expand Up @@ -121,10 +133,10 @@ jobs:
# verified base — GitHub only refreshes this REST field on PR
# synchronize events (e.g. a push to the head branch), so it can
# lag `main`'s actual tip by hours if the PR sits idle while main
# moves forward. `refs/pull/<n>/merge`, resolved in the next step,
# IS kept continuously current against `main`, so the base used
# for verification is instead re-resolved live from
# refs/heads/main in the "Resolve source" step below.
# moves forward. The base used for verification is instead
# re-resolved live from refs/heads/main in the "Resolve source"
# step below (see grid#66 for why that step also stopped trusting
# GitHub's precomputed refs/pull/<n>/merge for the same reason).
echo "${PR_JSON}" | jq '{
number: .number,
title: .title,
Expand All @@ -147,40 +159,56 @@ jobs:
id: source
env:
PR_NUM: ${{ inputs.pr_number }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
run: |
set -euo pipefail
REPO_URL="https://github.com/${{ github.repository }}.git"

if [[ -n "${PR_NUM}" ]]; then
# Resolve the merge ref and refs/heads/main in a single
# `git ls-remote` call, instead of trusting the PR REST
# resource's `.base.sha` (see note in "Query PR metadata") and
# instead of two sequential round-trips, which would leave a
# window for main to advance between them and make BASE_SHA
# point past the merge ref's actual first parent. One call
# resolves both refs from the same server-side view, making
# them atomically consistent with each other.
LS_OUTPUT=$(git ls-remote "${REPO_URL}" "refs/pull/${PR_NUM}/merge" refs/heads/main)
MERGE_SHA=$(echo "${LS_OUTPUT}" | awk '/refs\/pull\//{print $1}')
BASE_SHA=$(echo "${LS_OUTPUT}" | awk '/refs\/heads\/main/{print $1}')
if [[ -z "${MERGE_SHA}" ]]; then
echo "::error::Cannot resolve refs/pull/${PR_NUM}/merge"
echo "::error::The PR may have merge conflicts or the merge ref may not exist"
exit 1
fi
# Resolve refs/heads/main live instead of trusting the PR REST
# resource's `.base.sha` (see note in "Query PR metadata"),
# which can lag main's actual tip by hours while a PR sits idle.
BASE_SHA=$(git ls-remote "${REPO_URL}" refs/heads/main | awk '{print $1}')
if [[ -z "${BASE_SHA}" ]]; then
echo "::error::Cannot resolve refs/heads/main for base verification"
exit 1
fi

# Construct the merge locally from the two pinned commit SHAs
# instead of trusting GitHub's asynchronously-regenerated
# refs/pull/<n>/merge: that ref is only recomputed on a push to
# the PR branch, a merge-base change, or a 12h max-age timer,
# and can be observed lagging main's live tip well past the
# window either of those give (see grid#66 for a live-reproduced
# example -- unchanged 49+ minutes after main advanced). A full
# (unshallow) clone guarantees `git merge` can find the true
# merge-base between base and head regardless of how far apart
# they are; glb-e2e's "Reconstruct PR merge" step repeats this
# exact reconstruction (same inputs, same pinned identity/date/
# message below) to independently arrive at the identical SHA
# this step verifies it against.
git clone --quiet "${REPO_URL}" merge-check
git -C merge-check fetch --quiet origin "${BASE_SHA}" "${HEAD_SHA}"
git -C merge-check checkout --quiet "${BASE_SHA}"

MERGE_COMMIT_MESSAGE="Merge PR #${PR_NUM} head ${HEAD_SHA} into base ${BASE_SHA} (${MERGE_COMMIT_MESSAGE_SUFFIX})"
if ! GIT_AUTHOR_NAME="${MERGE_COMMIT_NAME}" GIT_AUTHOR_EMAIL="${MERGE_COMMIT_EMAIL}" GIT_AUTHOR_DATE="${MERGE_COMMIT_DATE}" \
GIT_COMMITTER_NAME="${MERGE_COMMIT_NAME}" GIT_COMMITTER_EMAIL="${MERGE_COMMIT_EMAIL}" GIT_COMMITTER_DATE="${MERGE_COMMIT_DATE}" \
git -C merge-check merge --no-ff --quiet -m "${MERGE_COMMIT_MESSAGE}" "${HEAD_SHA}"; then
echo "::error::PR #${PR_NUM} (head ${HEAD_SHA}) does not merge cleanly onto main (${BASE_SHA})"
exit 1
fi
MERGE_SHA=$(git -C merge-check rev-parse HEAD)

{
echo "source_kind=pr"
echo "source_label=PR #${PR_NUM}"
echo "tested_sha=${MERGE_SHA}"
echo "checkout_sha=${BASE_SHA}"
echo "base_sha=${BASE_SHA}"
} >> "${GITHUB_OUTPUT}"
echo "Resolved refs/pull/${PR_NUM}/merge -> ${MERGE_SHA}"
echo "Resolved refs/heads/main -> ${BASE_SHA} (expected base)"
echo "Resolved refs/heads/main -> ${BASE_SHA} (base)"
echo "Constructed deterministic merge of PR #${PR_NUM} head ${HEAD_SHA} onto ${BASE_SHA} -> ${MERGE_SHA}"
else
MAIN_SHA=$(git ls-remote "${REPO_URL}" refs/heads/main | awk '{print $1}')
if [[ -z "${MAIN_SHA}" ]]; then
Expand All @@ -191,6 +219,7 @@ jobs:
echo "source_kind=main"
echo "source_label=main"
echo "tested_sha=${MAIN_SHA}"
echo "checkout_sha=${MAIN_SHA}"
} >> "${GITHUB_OUTPUT}"
echo "Resolved refs/heads/main -> ${MAIN_SHA}"
fi
Expand Down Expand Up @@ -242,15 +271,35 @@ jobs:
# Checkout
# ----------------------------------------------------------------------

- name: Checkout Grid at tested SHA
- name: Checkout Grid at base revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.repository }}
ref: ${{ needs.resolve-source.outputs.tested_sha }}
ref: ${{ needs.resolve-source.outputs.checkout_sha }}
path: grid
fetch-depth: 2
fetch-depth: 0
persist-credentials: false

- name: Reconstruct PR merge
if: needs.resolve-source.outputs.source_kind == 'pr'
run: |
set -euo pipefail
REPO="${GITHUB_WORKSPACE}/grid"
git -C "${REPO}" fetch --quiet origin "${HEAD_SHA}"

# Must reproduce resolve-source's "Resolve source" step exactly
# (same base_sha/head_sha, same pinned identity/date/message) so
# the resulting commit SHA matches TESTED_SHA byte-for-byte --
# that match is what "Verify PR merge checkout" below asserts.
MERGE_COMMIT_MESSAGE="Merge PR #${PR_NUMBER} head ${HEAD_SHA} into base ${BASE_SHA} (${MERGE_COMMIT_MESSAGE_SUFFIX})"
if ! GIT_AUTHOR_NAME="${MERGE_COMMIT_NAME}" GIT_AUTHOR_EMAIL="${MERGE_COMMIT_EMAIL}" GIT_AUTHOR_DATE="${MERGE_COMMIT_DATE}" \
GIT_COMMITTER_NAME="${MERGE_COMMIT_NAME}" GIT_COMMITTER_EMAIL="${MERGE_COMMIT_EMAIL}" GIT_COMMITTER_DATE="${MERGE_COMMIT_DATE}" \
git -C "${REPO}" merge --no-ff --quiet -m "${MERGE_COMMIT_MESSAGE}" "${HEAD_SHA}"; then
echo "::error::Reconstructing the merge of PR #${PR_NUMBER} head ${HEAD_SHA} onto base ${BASE_SHA} failed"
echo "::error::This mirrors resolve-source's own merge, which already succeeded earlier in this run"
exit 1
fi

- name: Verify PR merge checkout
if: needs.resolve-source.outputs.source_kind == 'pr'
run: |
Expand Down
Loading