diff --git a/.github/workflows/e2e-label-dispatch.yml b/.github/workflows/e2e-label-dispatch.yml new file mode 100644 index 000000000..b58ca306a --- /dev/null +++ b/.github/workflows/e2e-label-dispatch.yml @@ -0,0 +1,65 @@ +name: E2E Label Dispatch + +# When a maintainer applies `test:e2e` or `test:e2e-gpu`, dispatch the matching +# self-hosted workflow against the copy-pr-bot mirror branch. Without this, +# the gated workflow only runs on push to `pull-request/`, so a label +# applied after the mirror was created leaves the gate stuck red until someone +# manually re-runs the workflow. +# +# Pushes to `pull-request/` (whether from an automatic copy-pr-bot sync or +# a maintainer-typed `/ok to test`) already re-trigger the gated workflow on +# their own - only the label-application case needs this dispatcher. +# +# Uses `pull_request_target` so forked PRs get a write-capable token. The job +# never checks out PR code; it only calls the GitHub API. + +on: + pull_request_target: + types: [labeled] + +permissions: {} + +jobs: + dispatch: + name: Dispatch E2E workflow for labeled PR + if: github.event.label.name == 'test:e2e' || github.event.label.name == 'test:e2e-gpu' + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + steps: + - name: Dispatch workflow against the copy-pr-bot mirror + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + LABEL_NAME: ${{ github.event.label.name }} + shell: bash + run: | + set -euo pipefail + + case "$LABEL_NAME" in + test:e2e) workflow=branch-e2e.yml ;; + test:e2e-gpu) workflow=test-gpu.yml ;; + *) echo "Unrecognized label $LABEL_NAME"; exit 1 ;; + esac + + mirror_ref="pull-request/$PR_NUMBER" + mirror_sha=$(gh api "repos/$GH_REPO/branches/$mirror_ref" --jq '.commit.sha' 2>/dev/null || echo "") + short_pr=${PR_HEAD_SHA:0:7} + + if [ -z "$mirror_sha" ]; then + gh pr comment "$PR_NUMBER" --body "Label \`$LABEL_NAME\` applied, but copy-pr-bot has not mirrored this PR yet. A maintainer needs to comment \`/ok to test $PR_HEAD_SHA\` to start the mirror; re-apply the label once \`$mirror_ref\` exists." + exit 0 + fi + + if [ "$mirror_sha" != "$PR_HEAD_SHA" ]; then + short_mirror=${mirror_sha:0:7} + gh pr comment "$PR_NUMBER" --body "Label \`$LABEL_NAME\` applied, but \`$mirror_ref\` is at \`$short_mirror\` while the PR head is \`$short_pr\`. Comment \`/ok to test $PR_HEAD_SHA\` to refresh the mirror, then re-apply the label." + exit 0 + fi + + echo "Dispatching $workflow against $mirror_ref ($short_pr) for label $LABEL_NAME." + gh workflow run "$workflow" --ref "$mirror_ref" + gh pr comment "$PR_NUMBER" --body "Dispatched \`$workflow\` against \`$mirror_ref\` at \`$short_pr\` (label \`$LABEL_NAME\`). Results will post as checks on this PR." diff --git a/CI.md b/CI.md new file mode 100644 index 000000000..2928458c7 --- /dev/null +++ b/CI.md @@ -0,0 +1,112 @@ +# CI + +This document describes how OpenShell's continuous integration works for pull requests, with a focus on what contributors need to do to get their PR tested. + +For local test commands see [TESTING.md](TESTING.md). For PR conventions see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Overview + +E2E tests run on self-hosted runners (`build-arm64`, GPU runners). To keep untrusted PR code off those runners we use NVIDIA's [copy-pr-bot](https://docs.gha-runners.nvidia.com/platform/apps/copy-pr-bot/), which mirrors trusted PRs to internal `pull-request/` branches. The gated workflows trigger on pushes to those branches, not on the original PR. + +Two opt-in labels enable the suites: + +- `test:e2e` runs `Branch E2E Checks` (non-GPU E2E) +- `test:e2e-gpu` runs `GPU Test` + +Both are required to merge once the corresponding `E2E Gate` checks are marked required in branch protection. + +## Commit signing + +copy-pr-bot decides whether to mirror a PR automatically based on whether the author is trusted. For org members and collaborators, "trusted" means **all commits in the PR are cryptographically signed**. Unsigned commits, even from an org member, force the bot to wait for a maintainer's `/ok to test `. + +DCO sign-off (`-s` / `Signed-off-by`) is a separate requirement and does not count as commit signing. + +### One-time setup with an SSH key + +If you already use an SSH key for `git push`, you can reuse it as a signing key. (You can also generate a separate one - GitHub allows the same SSH key as both auth and signing.) + +1. Generate a key (skip if reusing your existing SSH key): + + ```shell + ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_signing + ``` + +2. Add the **public** key at using **New SSH key**, and set **Key type: Signing Key** (not Authentication). Signing keys are managed separately from authentication keys, even when they reuse the same key material - you have to add the entry once for each role. + +3. Configure git globally: + + ```shell + git config --global gpg.format ssh + git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub + git config --global commit.gpgsign true + git config --global tag.gpgsign true + ``` + +4. (Recommended) Tell git to verify your own signatures locally by listing your key as trusted: + + ```shell + echo "$(git config user.email) $(cat ~/.ssh/id_ed25519_signing.pub)" \ + >> ~/.ssh/allowed_signers + git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers + ``` + +5. Verify on a test commit: + + ```shell + git commit --allow-empty -s -m "test: signing" + git log --show-signature -1 + ``` + + You should see `Good "git" signature for you@example.com`. + +If you have older unsigned commits on a branch, sign them by rebasing: + +```shell +git rebase --exec 'git commit --amend --no-edit -S' origin/main +``` + +## Pull request flows + +### Internal contributor PR + +Prerequisites: + +- Org member or collaborator on the repo. +- All commits cryptographically signed (see [Commit signing](#commit-signing)). +- All commits include a DCO sign-off (`git commit -s`). + +Flow: + +1. Open the PR. copy-pr-bot mirrors it to `pull-request/` automatically. +2. A maintainer applies `test:e2e` and/or `test:e2e-gpu`. +3. `E2E Label Dispatch` detects the label and triggers the matching workflow against the mirror. +4. Results post as checks on your PR head SHA. +5. New commits push to the mirror automatically; gated workflows re-run on their own. No re-labeling needed. + +### Forked PR + +Prerequisites: + +- DCO sign-off (`git commit -s`) on every commit. Commit signing is not required for forks - copy-pr-bot trusts forks based on maintainer review, not signing. +- A maintainer must vouch you. See the [Vouch System](AGENTS.md#vouch-system). + +Flow: + +1. Open the PR. The vouch check confirms you are vouched (otherwise the PR is auto-closed). +2. copy-pr-bot does not mirror forks automatically. A maintainer reviews the diff and comments `/ok to test ` with your latest commit SHA. +3. After `/ok to test`, copy-pr-bot mirrors to `pull-request/`. +4. A maintainer applies `test:e2e` / `test:e2e-gpu`. The dispatcher runs the matching workflow against the mirror. +5. Results post as checks on your PR. + +Important: every new commit you push requires another `/ok to test ` from a maintainer before E2E will run on it. If a label is applied while the mirror is stale, `E2E Label Dispatch` will post a comment explaining what's needed. + +## Workflow files + +| File | Role | +|---|---| +| `.github/workflows/branch-e2e.yml` | Non-GPU E2E. Triggers on `push: pull-request/[0-9]+`. | +| `.github/workflows/test-gpu.yml` | GPU E2E. Triggers on `push: pull-request/[0-9]+`. | +| `.github/actions/pr-gate/action.yml` | Composite action that resolves PR metadata and verifies the required label is set. | +| `.github/workflows/e2e-gate.yml` | Posts the required `E2E Gate` check on the PR. Re-evaluates after the gated workflow completes. | +| `.github/workflows/e2e-gate-check.yml` | Reusable gate logic shared by E2E and GPU E2E. | +| `.github/workflows/e2e-label-dispatch.yml` | Triggers gated workflows when a `test:e2e*` label is applied. Posts a comment if the mirror is missing or stale. | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c83d32efb..3a01d75eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -293,3 +293,9 @@ All contributions must include a `Signed-off-by` line in each commit message. Th ```bash git commit -s -m "feat(sandbox): add new capability" ``` + +DCO sign-off is separate from cryptographic commit signing. CI requires signing for org members so that copy-pr-bot can mirror your PR automatically; see [CI.md](CI.md#commit-signing) for setup. + +## CI + +How E2E runs in CI, the `test:e2e` / `test:e2e-gpu` labels, copy-pr-bot, and commit-signing setup are documented in [CI.md](CI.md).