ci: assert the scheduled refresh actually checked out main
CI / extension-version (push) Successful in 5s
CI / lint (push) Successful in 6s
Build images / sign-extension (push) Successful in 6s
Build images / build-ml (push) Successful in 9s
Build images / build-agent (push) Successful in 11s
Build images / build-web (push) Successful in 6s
CI / frontend-build (push) Successful in 18s
extension / lint (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Successful in 3m48s

BUILD_REF is read through the `env` context inside `with:`, which this
runner is not known to evaluate. `${{ steps.* }}` and `${{ secrets.* }}`
in `with:`/`env:` are proven here; `env` is not, and run 4915's checkout
log (`git checkout -B dev refs/remotes/origin/dev`) cannot tell an
honoured `refs/heads/dev` from an empty value falling back to the same
place — the two are indistinguishable on every path except the one that
matters.

If it does resolve empty, the weekly refresh checks out dev and pushes
its source to :latest, which is production. Every lane stays green and
the first symptom is production running code that was never merged.

So each of the four jobs now asserts its own checkout before doing
anything, gated on `github.event_name` — the `github` context is
demonstrably evaluated in `if:`, so the guard cannot be disabled by the
same uncertainty it covers. A red weekly job is an acceptable outcome;
shipping dev to production is not.
This commit is contained in:
2026-08-29 22:57:41 -04:00
parent 63e0a423d7
commit 6663e06aa6
2 changed files with 74 additions and 1 deletions
+69
View File
@@ -128,6 +128,33 @@ jobs:
# wrong, too-low value rather than failing (ci-requirements.md). # wrong, too-low value rather than failing (ci-requirements.md).
fetch-depth: 0 fetch-depth: 0
# BUILD_REF is what makes a scheduled run build `main` rather than the
# branch its cron fired from — and it is read through the `env` context
# inside `with:`, which this runner is NOT known to evaluate. If it does
# not, checkout silently falls back to the triggering ref and the weekly
# refresh publishes DEV's source to `:latest`, which is production.
# Every lane would stay green; the first sign of it would be production
# running code that was never merged.
#
# So assert the checkout instead of trusting the expression. A red
# weekly job is a fine outcome. Shipping dev to production is not.
#
# `if:` reads the `github` context, which the runner demonstrably does
# evaluate — this file already gates steps on it — so the guard cannot
# be disabled by the same uncertainty it exists to cover.
- name: Guard — a scheduled run must have checked out main
if: github.event_name == 'schedule'
run: |
set -eu
BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "schedule: HEAD is $BRANCH ($(git rev-parse --short HEAD))"
if [ "$BRANCH" != "main" ]; then
echo "schedule: expected main, got '$BRANCH'." >&2
echo "schedule: BUILD_REF was not honoured by the runner." >&2
echo "schedule: refusing to publish a channel tag from it." >&2
exit 1
fi
# The version is DERIVED, not read from the repo (milestone 271 step 4, # The version is DERIVED, not read from the repo (milestone 271 step 4,
# cut over 2026-08-27). `packaging.sh version` returns `YYYY.M.D.HHMM` # cut over 2026-08-27). `packaging.sh version` returns `YYYY.M.D.HHMM`
# UTC — the commit TIME of the newest change to a PACKAGED extension # UTC — the commit TIME of the newest change to a PACKAGED extension
@@ -408,6 +435,20 @@ jobs:
# that exists perfectly well under its real name. # that exists perfectly well under its real name.
fetch-depth: 0 fetch-depth: 0
# See sign-extension's copy for why this guard exists.
- name: Guard — a scheduled run must have checked out main
if: github.event_name == 'schedule'
run: |
set -eu
BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "schedule: HEAD is $BRANCH ($(git rev-parse --short HEAD))"
if [ "$BRANCH" != "main" ]; then
echo "schedule: expected main, got '$BRANCH'." >&2
echo "schedule: BUILD_REF was not honoured by the runner." >&2
echo "schedule: refusing to publish a channel tag from it." >&2
exit 1
fi
# --- derived values, one line (milestone 313) ------------------------ # --- derived values, one line (milestone 313) ------------------------
# These stopped being shadow output at step 3. `revision` decides # These stopped being shadow output at step 3. `revision` decides
# whether the build below runs at all and `version` is what the image # whether the build below runs at all and `version` is what the image
@@ -907,6 +948,20 @@ jobs:
# the build would otherwise notice. # the build would otherwise notice.
fetch-depth: 0 fetch-depth: 0
# See sign-extension's copy for why this guard exists.
- name: Guard — a scheduled run must have checked out main
if: github.event_name == 'schedule'
run: |
set -eu
BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "schedule: HEAD is $BRANCH ($(git rev-parse --short HEAD))"
if [ "$BRANCH" != "main" ]; then
echo "schedule: expected main, got '$BRANCH'." >&2
echo "schedule: BUILD_REF was not honoured by the runner." >&2
echo "schedule: refusing to publish a channel tag from it." >&2
exit 1
fi
# --- derived values, one line (milestone 313) ------------------------ # --- derived values, one line (milestone 313) ------------------------
# These stopped being shadow output at step 3. `revision` decides # These stopped being shadow output at step 3. `revision` decides
# whether the build below runs at all and `version` is what the image # whether the build below runs at all and `version` is what the image
@@ -1260,6 +1315,20 @@ jobs:
# the build would otherwise notice. # the build would otherwise notice.
fetch-depth: 0 fetch-depth: 0
# See sign-extension's copy for why this guard exists.
- name: Guard — a scheduled run must have checked out main
if: github.event_name == 'schedule'
run: |
set -eu
BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "schedule: HEAD is $BRANCH ($(git rev-parse --short HEAD))"
if [ "$BRANCH" != "main" ]; then
echo "schedule: expected main, got '$BRANCH'." >&2
echo "schedule: BUILD_REF was not honoured by the runner." >&2
echo "schedule: refusing to publish a channel tag from it." >&2
exit 1
fi
# --- derived values, one line (milestone 313) ------------------------ # --- derived values, one line (milestone 313) ------------------------
# These stopped being shadow output at step 3. `revision` decides # These stopped being shadow output at step 3. `revision` decides
# whether the build below runs at all and `version` is what the image # whether the build below runs at all and `version` is what the image
+5 -1
View File
@@ -178,7 +178,11 @@ per `docs/process.md`'s "add deps to the image when used by >1 project".
BUILD_REF` that every checkout in the file takes, rather than per job — BUILD_REF` that every checkout in the file takes, rather than per job —
otherwise `sign-extension` would derive dev's extension version while otherwise `sign-extension` would derive dev's extension version while
`build-web` bundled main's, and the release download would 404 on a version `build-web` bundled main's, and the release download would 404 on a version
that exists perfectly well. that exists perfectly well. Every job then ASSERTS its checkout is `main`
before doing anything, because `env` inside `with:` is not a context this
runner is known to evaluate — if it silently resolved to empty, checkout
would fall back to the triggering ref and the refresh would publish dev's
source to `:latest` with every lane green.
- It **publishes only `:latest`.** `:c-<sha>` for main's HEAD already names - It **publishes only `:latest`.** `:c-<sha>` for main's HEAD already names
the bytes that commit built; re-pushing it over refreshed layers would the bytes that commit built; re-pushing it over refreshed layers would
break the one tag rule 145 makes immutable, and it is the rollback unit. break the one tag rule 145 makes immutable, and it is the rollback unit.