Files
pezkuwi-wallet-utils/.github/workflows/promote-to-live.yml
T
SatoshiQaziMuhammed 07aa8b3dd0 Purge changed files from jsDelivr after promoting to master (#77)
* feat(config): sign the served config files

signed/targets.json gives the sha256 of every JSON file the wallet may
fetch, signed with an Ed25519 targets key. signed/root.json names that
key and three sr25519 root keys (wallet accounts), two of which can
replace it; the wallet will ship this first root.

scripts/signed_config.py writes the root, signs the targets list and
checks it. Code Quality now refuses a tree whose served files do not
match the signed list, and runs weekly so an expiring signature shows a
month ahead.

* fix(config): sign the test fixtures and follow published roots

The wallet refuses any JSON file from this repository that the signed
list does not give, and its instrumented tests read tests/, so those
files are signed too.

A root after the first is written to signed/root.N.json, where the
wallet looks for it; sign and check use the newest published root.

* Purge changed files from jsDelivr after promoting to master

When raw.githubusercontent.com is unreachable the wallet reads the same paths
from jsDelivr, which caches a branch file for up to 12 hours and expires each
file on its own. A stale chains.json next to a fresh signed/targets.json fails
the wallet's hash check, so the wallet keeps its old config until both expire.

After master moves, purge every JSON file that changed, and fail the job if
any CDN behind jsDelivr did not purge, or throttled, a path.
2026-11-01 11:40:55 +04:00

91 lines
3.9 KiB
YAML

name: Promote main → master
# master is the live branch: every app reads its config straight from
# raw.githubusercontent.com/.../master/... . Nothing should reach it that has not
# passed Code Quality first.
#
# The flow used to run the other way. auto-pr.yml fired on a push to *master* and
# opened a master → main PR, so content was already live before a single check ran —
# the gate sat downstream of the thing it was meant to gate. A check that reports
# "the live config is broken" is not a gate.
#
# It also made master unwritable by the front door: five checks were required there and
# no workflow triggered on a PR into master, so the only way to change the live branch
# was to bypass its own protection. A rule that can only be bypassed teaches people to
# bypass it.
#
# Now: work lands on main, Code Quality decides, and only then is master moved to match.
on:
workflow_run:
workflows: ["Code Quality"]
types: [completed]
permissions:
contents: write
jobs:
promote:
# Only a green run, only on main, and only for a real push — a pull_request run
# tests a merge commit that does not exist on main yet.
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout the exact commit that passed
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- name: Refuse to promote a commit that is not on main
run: |
git fetch origin main
if ! git merge-base --is-ancestor "${{ github.event.workflow_run.head_sha }}" origin/main; then
echo "::error::${{ github.event.workflow_run.head_sha }} is not an ancestor of main — refusing"
exit 1
fi
echo "Commit is on main."
- name: Promote
run: |
SHA="${{ github.event.workflow_run.head_sha }}"
git fetch origin master
git rev-parse origin/master > "$RUNNER_TEMP/previous-master"
# --force-with-lease, not --force: if master has moved since this job read it,
# the push is refused rather than silently discarding whatever moved it.
git push --force-with-lease origin "$SHA":refs/heads/master
echo "master is now $SHA"
echo "### Promoted to live" >> "$GITHUB_STEP_SUMMARY"
echo "\`master\` → \`$SHA\`" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Apps read this branch directly; the change is live as soon as their config refreshes." >> "$GITHUB_STEP_SUMMARY"
- name: Purge the changed files from jsDelivr
# When raw.githubusercontent.com is unreachable the wallet reads the same paths from
# jsDelivr, which keeps a branch file for up to 12 hours and expires each file on its
# own clock. A cached chains.json next to a fresh signed/targets.json (or the reverse)
# fails the wallet's hash check, and the wallet keeps its old config until both age
# out. Purging every changed file together makes the mirror serve the new set at once.
run: |
SHA="${{ github.event.workflow_run.head_sha }}"
PREVIOUS=$(cat "$RUNNER_TEMP/previous-master")
failed=0
for path in $(git diff --name-only "$PREVIOUS" "$SHA" -- '*.json'); do
url="https://purge.jsdelivr.net/gh/${{ github.repository }}@master/$path"
# Each CDN behind jsDelivr reports on its own; a throttled or failed one still
# serves the old copy.
if curl -sSf -m 30 --retry 3 "$url" | jq -e '[.paths[] | (.throttled | not) and ([.providers[]] | all)] | all' > /dev/null; then
echo "purged $path"
else
echo "::error::jsDelivr purge failed for $path"
failed=1
fi
done
exit $failed