mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
* Keep reading an older receipt at the top of a PR body, and leave quoted receipt examples in place Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Put a receipt posted on its own inside the PR body's Agent notes, so the new one is the one read Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Read the PR body from the event file so a very long body cannot break the title check Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Make the Plain title check required before gstack merges a PR Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep the PR's opening paragraph when a posted receipt moves into the Agent notes Review-Finding: v1/adversarial-native#1 reader=adversarial-native Review-Finding: v1/outside-voice#1 reader=outside-voice Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Say exactly when an older receipt at the top of a PR body is still read The older receipt is read only while it still opens the body. A receipt quoted in the readable part is never read only when the body has an Agent notes section. The example policy now lists the Plain title check. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Add the changelog entry for the required Plain title check Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
90 lines
3.9 KiB
YAML
90 lines
3.9 KiB
YAML
name: Plain title
|
|
|
|
# Checks that a PR's title reads in everyday words (R76, docs/ship-policy.md#plain-titles).
|
|
# The title is refused in the front: tracker codes, project-only words and a
|
|
# type(scope): prefix. One trailing group of references like (ABC-123) or (#4122)
|
|
# is fine. The branch name, the PR opening and the commit subjects only warn.
|
|
# PRs created before the policy's since date are exempt.
|
|
#
|
|
# WHY pull_request_target: it runs the base repository's copy of this workflow,
|
|
# of bin/gstack-plain-title and of .gstack/plain-titles.json, so a PR cannot
|
|
# change the rule it is checked against, and a title edit reruns the check.
|
|
#
|
|
# WHY this is SAFE:
|
|
# - The job checks out the BASE repo (no ref:) and never runs PR code.
|
|
# - The token is read-only (contents: read, pull-requests: read).
|
|
# - The title, branch, body and created_at arrive only through env: and are
|
|
# written to files with printf '%s'. No PR field is inlined into run:.
|
|
# - Only the checker's match lines reach the log; PR text is never echoed.
|
|
# test/plain-title-workflow-safety.test.ts is the static tripwire for all of it.
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, edited, synchronize, reopened, ready_for_review]
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
plain-title:
|
|
name: Plain title
|
|
# No job-level if: every event, a base change included, reruns the title
|
|
# check, because a skipped run would count as passing over a refused title.
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
# A newer event for the same PR cancels the older run.
|
|
concurrency:
|
|
group: plain-title-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
# Base repo only: trusted code. No PR-head checkout.
|
|
- name: Checkout base repo (trusted)
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
with:
|
|
bun-version: 1.4.2
|
|
|
|
- name: Write the PR fields to files
|
|
env:
|
|
# Attacker-controlled on fork PRs: env only, never inlined into run:.
|
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
PR_NUM: ${{ github.event.pull_request.number }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
printf '%s' "$PR_TITLE" > "$RUNNER_TEMP/pr-title.txt"
|
|
# The body comes from the event file: a large body overflows one environment string (E2BIG).
|
|
jq -j '.pull_request.body // ""' "$GITHUB_EVENT_PATH" > "$RUNNER_TEMP/pr-body.txt"
|
|
# Commit subjects are a warning only: skip quietly when the API read fails.
|
|
if ! gh api --paginate "repos/$REPO/pulls/$PR_NUM/commits" \
|
|
--jq '.[] | select((.parents | length) < 2) | .commit.message | split("\n")[0]' \
|
|
> "$RUNNER_TEMP/pr-commits.txt" 2>/dev/null; then
|
|
: > "$RUNNER_TEMP/pr-commits.txt"
|
|
fi
|
|
|
|
- name: Check the title
|
|
env:
|
|
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
|
|
run: |
|
|
set -euo pipefail
|
|
bun --no-env-file --config=/dev/null bin/gstack-plain-title check \
|
|
--title-file "$RUNNER_TEMP/pr-title.txt" --created-at "$PR_CREATED_AT" --mode refuse
|
|
|
|
- name: Check the branch name, opening and commit subjects (warnings only)
|
|
if: ${{ !cancelled() }}
|
|
env:
|
|
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
|
|
run: |
|
|
set -euo pipefail
|
|
bun --no-env-file --config=/dev/null bin/gstack-plain-title check \
|
|
--branch "$PR_BRANCH" --body-file "$RUNNER_TEMP/pr-body.txt" \
|
|
--commits-file "$RUNNER_TEMP/pr-commits.txt" --created-at "$PR_CREATED_AT" --mode warn
|