ci: publish site/ to GitHub Pages, and gate the bytes that get served - #7
Merged
Conversation
site/ is tracked and is built from CAL FIRE's acquired files, which are not in git and never in CI, so this workflow cannot regenerate site/ and diff it. It checks what can be checked without those files, and says plainly what it cannot. The build job builds from the committed fixtures twice and requires the two runs to be byte-identical, then runs the new tests/test_published_site.py over the committed site/. Those tests require the published artifacts to be a measurement of the real files rather than fixture output, and require the provenance they publish to still equal the reviewed constants in src/perimeter/sources.py, so an acquisition that bumps a version, a byte count or a hash without a rebuild fails instead of being published. They also require every link to be relative, because the site is served from /perimeter/ and an href rooted at / lands outside it, a break that is invisible when the pages are opened from a directory. html-validate and axe-core then run over site/ itself, so what gets served is what passed the WCAG gate rather than a fixture build that resembles it. Top-level permissions are empty, each job takes only what it needs, and every action is pinned to the commit SHA of its tag. Pages still has to be pointed at GitHub Actions in the repository settings before the first deploy can succeed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sjkAHcKuBJoxvzc7D6XMA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
.github/workflows/pages.yml, which publishes the committedsite/to GitHub Pages on every push tomain, plustests/test_published_site.py, which is what makes that publication conditional on something.The honest shape of the staleness check
The brief was to regenerate
site/in the workflow and fail if the regenerated output differs from what is committed. That is not available here, and the difference is worth stating rather than papering over:site/is built from CAL FIRE's acquired files underdata/raw/, which is gitignored and never in CI. A workflow with no network and no acquired files cannot reproduce it.make site-offlinebuilds from the committed fixtures, which is a different measurement withis_fixture: truestamped on it, and publishing that would be exactly the substitution this project's design goes out of its way to prevent.So the build job checks everything that can be checked without those files:
ci.ymlruns, now also standing between the repository and the web.tests/test_published_site.pyrequiresis_fixture: falsein both artifacts, and requires each artifact'ssourceblock to equal whatperimeter.artifacts._source_jsoncomputes from the reviewed constants insrc/perimeter/sources.py. That is the realistic staleness failure caught: a new acquisition bumps a version, record count, byte count, hash or quoted caveat, andsite/is not rebuilt. It fails here.html-validateandnode tools/a11y.mjsrun oversite/itself, not over a fixture build that resembles it.What is not claimed, and the test module says so in its own docstring: none of this proves
site/is what the current pipeline would produce from CAL FIRE's files. Only a machine holding those files can settle that. I settled it once, locally, on this branch:python -m perimeter.cli --perimeters data/raw/frap_perimeters.json --dins data/raw/dins_postfire.jsoninto a scratch directory, thendiff -ragainstsite/, and the rebuild is byte-for-byte identical to what is committed.The subpath check
The site is served from
chelseakr.github.io/perimeter/, where anhrefrooted at/lands outside the site and 404s, and nothing about that is visible locally where the pages are opened from a directory.test_no_published_link_is_rooted_at_the_servermakes that permanent.Auditing the current pages found nothing to fix. Every internal link is a bare filename (
index.html,perimeters.html,dins.html,#content), the only absolute URLs are external ones to data.cnra.ca.gov and data.ca.gov, there are nosrcattributes at all, and the CSS is inline. One note on the brief: the pages do not referencesite/data/*.json, in prose or as links. Those two artifacts will publish at/perimeter/data/perimeters-coverage.jsonand/perimeter/data/dins-coverage.jsonand resolve fine, but nothing on the pages points a reader at them. Linking them would change rendered output, and rendered output can only be regenerated on a machine holdingdata/raw/, so I left it alone rather than committing HTML this branch cannot rebuild. Worth a follow-up on a branch where the files are in hand.Supply chain and permissions
permissions: {}, scoped per job:contents: readon build,pages: writeandid-token: writeon deploy only. The job that runs repository code holds neither deploy scope.concurrency: group: pages,cancel-in-progress: false.@<sha> # vX.Y.Zformci.ymlalready uses.actions/checkoutv4.4.0,astral-sh/setup-uvv5.4.2 andactions/setup-nodev4.4.0 reuse the pins already in this repository;configure-pagesv5.0.0,upload-pages-artifactv3.0.1 anddeploy-pagesv4.0.5 were resolved 2026-08-07 viagh api repos/OWNER/REPO/git/matching-refs/tags.persist-credentials: falseon checkout.Checks run before opening this
make verifypasses: ruff, ruff format, mypy strict, 450 tests, 100% coverage against a 90% floor, pip-audit, npm audit,html-validateandaxe-core.tests/test_published_site.pyis 17 passed, andhtml-validateandaxe-coreare clean oversite/(3 pages, 6 rule sets).zizmoron the new workflow: no findings at the default persona. At--persona=pedanticthe only two remaining are informationalanonymous-definitionnotes about unnamed jobs, which matches howci.ymlis already written.test_every_published_page_says_it_is_not_affiliated_with_cal_fireover the committed bytes, in addition to the existing check over built pages.One settings step is still needed, and only Chelsea can do it
This PR cannot enable Pages, and nothing here was deployed. After merge:
Settings > Pages > Build and deployment > Source > GitHub Actions.
The first
publish-siterun after that lands the site at:https://chelseakr.github.io/perimeter/
Repository visibility was not touched. It is public and stays public.
🤖 Generated with Claude Code
https://claude.ai/code/session_011sjkAHcKuBJoxvzc7D6XMA