Skip to content
Merged
Show file tree
Hide file tree
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
100 changes: 100 additions & 0 deletions .github/workflows/_probe-workflow-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Probe (workflow_run)

# TEMPORARY EXPERIMENT — delete once the question below is answered.
#
# QUESTION: does a Pages deploy triggered by `workflow_run` update the served ORIGIN
# when that SHA has already been deployed?
#
# Why we can't reason it out. `workflow_run` sets GITHUB_SHA to the DEFAULT BRANCH's HEAD,
# not the triggering commit (GitHub's events table; confirmed on scikit-learn, whose
# consecutive workflow_run runs from different PRs all carry the same head_sha). And
# actions/deploy-pages stamps pages_build_version = GITHUB_SHA, which per
# actions/deploy-pages#383 is silently no-op'd when it repeats. Together that *suggests*
# at most one serving deploy per merge to the default branch.
#
# But that inference is unsafe: a repeat `workflow_dispatch` at an already-deployed SHA
# demonstrably DOES re-serve — the whole re-dispatch trampoline in publish.yml depends on
# it and it works in production. So event type matters beyond pages_build_version, in a
# way #383 doesn't explain, and workflow_run might behave like workflow_dispatch. The
# Pages deployment RECORD is created either way (so the API can't tell us); only the
# origin can. Hence a real deploy against the real site.
#
# THE OBSERVATIONS (see the plan; read the answer from switcher.json at the origin):
# 1. push to main → first deploy of that SHA; should serve. Baseline, and it
# proves the gather works with no `version-name` injection.
# 2. tag v0.0.0a1 on that same main commit → SECOND deploy of the SAME SHA, but now
# assembling a site that CONTAINS v0.0.0a1. Different bytes,
# same SHA — the real-world tag case. If the origin gains
# v0.0.0a1, workflow_run re-serves and the migration is on.
# 3. a docs PR, pushed twice → both deploys land at the same unchanged main HEAD. Does
# /pr-<n>/ appear, and does it UPDATE on the second push?
# Highest-volume case; first to break if the collision is real.
#
# Safety: this deploys the normal, correctly assembled site — the only question is whether
# the origin moves. Worst case it goes stale (not broken), and publish.yml's
# "Verify the deployed origin" step turns that into a red check rather than a silent lie.
# Recovery is a manual workflow_dispatch of publish-dispatch.yml, which we know re-serves.
on:
workflow_run:
workflows: [CI] # matched by workflow NAME (ci.yml's `name:`), not filename
types: [completed]

jobs:
# Always runs, even for a failed/fork CI run: the context dump is half the experiment.
# `github.sha` here is the value deploy-pages will use as pages_build_version.
context:
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Log the workflow_run context
env:
WR: ${{ toJSON(github.event.workflow_run) }}
run: |
set -euo pipefail
printf '%-34s %s\n' \
'github.sha (→ pages_build_version)' '${{ github.sha }}' \
'github.ref' '${{ github.ref }}' \
'github.ref_name' '${{ github.ref_name }}' \
'github.event_name' '${{ github.event_name }}' \
'— triggering run —' '' \
'workflow_run.id' '${{ github.event.workflow_run.id }}' \
'workflow_run.event' '${{ github.event.workflow_run.event }}' \
'workflow_run.conclusion' '${{ github.event.workflow_run.conclusion }}' \
'workflow_run.head_branch' '${{ github.event.workflow_run.head_branch }}' \
'workflow_run.head_sha' '${{ github.event.workflow_run.head_sha }}' \
'head_repository' '${{ github.event.workflow_run.head_repository.full_name }}'
# The comparison that matters: if github.sha != workflow_run.head_sha, the deploy
# is stamped with something other than the commit that was actually built.
if [ '${{ github.sha }}' = '${{ github.event.workflow_run.head_sha }}' ]; then
echo "::notice::github.sha MATCHES the triggering commit — no collision risk"
else
echo "::notice::github.sha (${{ github.sha }}) differs from the built commit \
(${{ github.event.workflow_run.head_sha }}) — every deploy between merges reuses this SHA"
fi
echo "--- full workflow_run payload ---"
printf '%s\n' "$WR"

# The actual experiment: deploy through the REAL engine, so the site content is correct
# and publish.yml's Verify step reports the answer in-run.
#
# version-name: "" → pure durable gather. Unlike the inline path, the triggering run has
# COMPLETED, so its `docs` artifact is discoverable by the normal gather (by default-branch
# or by PR head SHA) with no injection. Whether that actually holds is observation 1.
#
# The fork guard is not optional: workflow_run hands a write token to a job triggered by
# fork-PR code (the pwn-request footgun that publish.yml's read-only `warn` job exists to
# avoid). Fork CI runs are excluded here and stay excluded.
deploy:
needs: [context]
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository
uses: ./.github/workflows/publish.yml
with:
version-name: ""
permissions:
contents: read
actions: write
pages: write
id-token: write
statuses: write
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@ jobs:
# fork PR's token is read-only, so untrusted code never reaches a write token.
# `actions: write` lets the re-dispatch job re-fire the shim; `version-name` is the
# in-run build for the inline path (ignored by the re-dispatch/warn branches).
# TEMPORARILY DISABLED — _probe-workflow-run.yml's experiment needs the workflow_run path
# to be the ONLY deploy path, so the experiment has one variable. Tags are disabled here
# too, deliberately: the re-dispatch job would deploy via workflow_dispatch, which we
# already know re-serves, and that is precisely the confound observation 2 must avoid.
# RESTORE to:
# if: github.repository == 'DiamondLightSource/myst-version-switcher-plugin'
# when the probe is deleted.
publish:
needs: [lint, test, docs]
if: github.repository == 'DiamondLightSource/myst-version-switcher-plugin'
if: false
uses: ./.github/workflows/publish-dispatch.yml
with:
version-name: ${{ needs.docs.outputs.version-name }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ jobs:

# Internal PR / default-branch push (inline), or any workflow_dispatch → deploy.
deploy:
# `workflow_run` is TEMPORARY — _probe-workflow-run.yml's experiment (delete both once
# it has answered). Its caller already gates on conclusion + non-fork; the deploy
# itself needs no other change, because version-name "" is an existing supported mode.
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'workflow_run' ||
(github.event_name == 'push' && github.ref_type != 'tag') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository)
Expand Down