name: Docs Snapshots Guard on: pull_request: paths: - "docs/**" permissions: contents: read pull-requests: read jobs: guard: name: Protect frozen snapshots and append-only assets runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - name: Determine merge base id: base run: | base_sha="$(git merge-base "origin/${{ github.event.pull_request.base.ref }}" HEAD)" echo "sha=$base_sha" >> "$GITHUB_OUTPUT" - name: Detect escape-hatch label id: escape env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_TITLE: ${{ github.event.pull_request.title }} run: | # The [docs-freeze] marker (in the PR title) is the only way to # legitimately modify frozen snapshots or remove published assets. # Detect it from the title since the workflow runs on # pull_request (not pull_request_target) and can't always read # labels reliably. if [[ "$PR_TITLE" == *"[docs-freeze]"* ]]; then echo "allowed=true" >> "$GITHUB_OUTPUT" else echo "allowed=false" >> "$GITHUB_OUTPUT" fi - name: Guard frozen snapshots env: ALLOWED: ${{ steps.escape.outputs.allowed }} BASE_SHA: ${{ steps.base.outputs.sha }} run: | set -euo pipefail # Anything under docs/v/ is a frozen release snapshot and # must not change after the release-cut PR that introduced it. # The release-cut PR uses the [docs-freeze] title prefix to opt # out of this guard. ``docs/v[0-9]*/**`` is the defensive form so # we never catch a hypothetical ``docs/vendor/`` etc. violations="$(git diff --name-only --diff-filter=AMDRT \ "$BASE_SHA"..HEAD -- 'docs/v[0-9]*/**' || true)" if [[ -z "$violations" ]]; then echo "OK: no changes under docs/v*/" exit 0 fi if [[ "$ALLOWED" == "true" ]]; then echo "OK: [docs-freeze] PR is allowed to touch docs/v*/:" echo "$violations" exit 0 fi echo "::error::This PR modifies frozen release snapshots under docs/v*/." echo "Frozen snapshots are immutable. To intentionally edit a snapshot" echo "(e.g. a release-cut PR generated by 'devtools release' or the" echo "manual 'scripts/docs/freeze_current_edge.py' wrapper), prefix" echo "the PR title with [docs-freeze]." echo echo "Offending files:" echo "$violations" exit 1 - name: Guard append-only images env: ALLOWED: ${{ steps.escape.outputs.allowed }} BASE_SHA: ${{ steps.base.outputs.sha }} run: | set -euo pipefail # Deleting or renaming an image breaks every frozen snapshot that # still references it (snapshots reuse docs/images/ at the docs # root). Only [docs-freeze] PRs are allowed to do that. deletions="$(git diff --name-only --diff-filter=DR \ "$BASE_SHA"..HEAD -- 'docs/images/**' || true)" if [[ -z "$deletions" ]]; then echo "OK: no images deleted or renamed." exit 0 fi if [[ "$ALLOWED" == "true" ]]; then echo "OK: [docs-freeze] PR is allowed to delete/rename images:" echo "$deletions" exit 0 fi echo "::error::This PR deletes or renames files under docs/images/." echo "Images are append-only because frozen snapshots in docs/v*/" echo "share a single docs/images/ directory and would break if an" echo "asset they reference disappears or moves." echo echo "If the asset is wrong, add a new file with a new name and" echo "reference the new name in Edge (docs/edge//...). Leave" echo "the old file in place so historical snapshots keep rendering." echo echo "Offending files:" echo "$deletions" exit 1