Skip to content

cf-pages

cf-pages #493

Workflow file for this run

# Deploy the results site to Cloudflare Pages (onthebench.ai).
#
# Replaces the GitHub Pages workflow (pages.yml). Once this is confirmed green
# and the nameserver cutover to Cloudflare is complete, pages.yml can be deleted.
#
# ONE ARTIFACT, ONE PASS. gen-data.mjs scans gateways/*/definition.json + results/**/*.json and
# emits site/data.json; the finished site/ directory is what wrangler uploads. Charts are drawn in
# the browser from that same bundle, so a `results/` commit deploys data and charts that cannot
# disagree - they are the same file.
#
# This used to build the bundle, shell out to charts.py to redraw 25 PNGs, then run gen-data AGAIN
# to copy them back in, and it depended on render-charts.yml's timing not to leave the deployed
# images a push behind. On 2026-07-31 that pipeline silently failed to regenerate and shipped one
# run's charts beside another run's numbers.
#
# Requires two repo secrets (Settings -> Secrets and variables -> Actions):
# CLOUDFLARE_API_TOKEN token with Pages:Edit on the account
# CLOUDFLARE_ACCOUNT_ID 91612b184a379a695266d84761cbee8f
name: cf-pages
on:
push:
branches: [main]
paths:
- "results/**"
- "gateways/**"
- "site/**"
# Without this it did not: the only workflow it triggered was render-charts, whose
# regenerated-PNG commit carries [skip ci], which this file's own header says it does not want
# to depend on. The chart code could change and the deployed board keep the old images.
- ".github/workflows/cf-pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: cf-pages
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
# NEVER PUBLISH A MIXED BOARD; PUBLISH n/a INSTEAD.
#
# C8 refuses a board whose columns were measured by different harness engines, and it is right to:
# a ranking across two instruments compares the instruments as much as the gateways. Its only two
# exits were both bad - re-measure the entire field (hours and real money to republish numbers that
# did not change), or override the guard and ship the mix anyway.
#
# This is the third exit, and it is the publishing POLICY for this board: show what the current
# engine measured, show n/a for what it has not reached yet, and say so on the row and in the
# bundle. A partial re-run therefore publishes AS IT LANDS - each gateway appears the moment it is
# re-measured - and the board is single-instrument at every intermediate state rather than at the
# end only. Declared here, in the workflow, so the policy is committed and reviewable rather than
# living in whatever shell happened to run the build.
#
# On a field where every row is already current this is a no-op: nothing is suppressed and the
# bundle records `suppressed_for_engine: []`.
env:
OTB_SINGLE_ENGINE: "1"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
- uses: actions/setup-python@v6
with:
python-version: "3.12"
# ORDER MATTERS (single canonical source): gen-data.mjs FIRST emits site/data.json, the
# canonical per-gateway passthrough/translation records (matrix per-cell sweep, with
# perf/xlate-suite fallback). The site READS that bundle, so every chart shows exactly
# the numbers the site table ranks. The second gen-data pass copies the fresh PNGs into
# site/charts/ and re-stamps data.json; both artifacts come from the one results tree in
# this same checkout, so they can never diverge. The consistency guard (site/test.mjs)
# fails the deploy if any surface would resolve a metric to a different value.
# Stars are refreshed AT BUILD TIME so the deployed board is exact at publish (Matthew's
# rule: 100% at time of build). On any API failure the committed snapshot stays - the
# script throws BEFORE writing, so a rate-limited build deploys the last-good counts.
- name: Refresh star snapshot (live at build; committed snapshot is the fallback)
env:
GITHUB_TOKEN: ${{ github.token }}
run: node gateways/fetch-stars.mjs || echo "stars refresh failed; deploying the committed snapshot"
# ONE PASS NOW, not three. This used to build the bundle, run charts.py to redraw 25 PNGs from
# it, then run gen-data AGAIN to copy those PNGs in and re-stamp them. The Charts tab draws from
# the bundle in the browser, so there is nothing to redraw and nothing to copy - and the deploy
# no longer installs matplotlib to render images the site does not read.
- name: Generate canonical site data
run: node site/gen-data.mjs
# THE SHIPPED BUNDLE, not a regenerated stand-in. site/test.mjs below re-runs gen-data into a
# temp dir and checks THAT bundle; the file wrangler actually uploads is site/data.json, which
# additionally went through the second gen-data pass (PNG copy + re-stamp). Running the lints'
# CLI against the artifact closes that gap. It was a path trigger on bench-tests.yml while no
# workflow ever executed it as a step - a gate named in a trigger list and run nowhere is the
# same decoration this repo keeps finding.
- name: Structural invariants C1-C5 on the bundle being deployed
run: node site/check-consistency.mjs site/data.json
# Both site suites also run in bench-tests.yml's `site` job, which has no bundle build in
# front of them - that is where they gate the SOURCE. This copy gates the DEPLOY: nothing
# ships that has not just been checked in the tree it ships from.
- name: Site coverage siblings (URL codec fixed points, one envelope one story)
run: node site/test-coverage.mjs
- name: Consistency guard (table == drawer == compare == charts)
run: node site/test.mjs
- name: Deploy to Cloudflare Pages
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler@4 pages deploy site --project-name=onthebench --branch=main