Skip to content

The site is deployed by one script, scripts/site-deploy, from a terminal or from the publication workflow; it refuses a dirty tree, a commit master does not contain and a build that is not HEAD's, pushes site/public to gh-pages with the source commit named, and pushes nothing when the output is unchanged

scripts/site-deploy is the only way the site reaches GitHub Pages. The Pages workflow runs it after its gate; a person runs it when the operator wants the site live without waiting for the Actions queue (.ai/repo/skills/deploy-site/SKILL.md, just site-deploy). Either way the same checks run and the same branch is pushed: gh-pages, which GitHub Pages serves.

guaranteed Deterministic and blocking. Implemented, and a behavioural test proves it.

The case runs the whole path against a local bare remote, no network; the footer of every page names the commit, so a deploy is verifiable with curl. The workflow calls the same script with --skip-build after its gate. Pointing GitHub Pages at the branch is a one-time --configure-pages. The hand procedure is .ai/repo/skills/deploy-site/SKILL.md.

What it means

scripts/site-deploy is the only way the site reaches GitHub Pages. The Pages workflow runs it after its gate; a person runs it when the operator wants the site live without waiting for the Actions queue (.ai/repo/skills/deploy-site/SKILL.md, just site-deploy). Either way the same checks run and the same branch is pushed: gh-pages, which GitHub Pages serves.

How it works

The script refuses (exit 10, nothing pushed) a working tree with uncommitted changes, a HEAD that origin/master does not contain (--any-ref publishes a preview and says so), and a site/public whose footer does not name HEAD. Unless --skip-build, it runs the gate: scripts/generate-site-data --check, majordomus generate --check when cargo is present, scripts/site-build, scripts/site-check, and the browser probe with --probe. It then copies site/public into a worktree of gh-pages (created orphan when the branch does not exist), adds .nojekyll, commits with the source commit and the registry fingerprint in the message, and pushes. Unchanged output is not committed again; --dry-run shows the commit it would push. --configure-pages points GitHub Pages at the branch once, through gh.

How to see it

scripts/site-deploy --dry-run       # gate, build, check; "would push <sha> to origin/gh-pages: deploy: site from <source>"
scripts/site-deploy                 # the same, then the push; GitHub serves it in about a minute
curl -s https://majordomus.dev/ | grep -c "/commit/$(git rev-parse HEAD)"   # 1
git log -1 --format='%an %s' origin/gh-pages    # github-actions[bot] for a workflow deploy, a person for a hand one

What it does not cover

A preview is published, not prevented. --any-ref exists so that unmerged work can be looked at on the real host, so after one the live site is a commit origin/master does not contain, and it stays that way until the next push to master publishes over it. That is the intended lifetime, not a leak: the page footer names the commit it was built from, the gh-pages commit message says deploy: site from <sha>, and its author is the person who ran the script rather than github-actions[bot], so what is published can always be read off the site itself. Reading the footer or that commit is how you tell a preview from master, and either is faster than comparing routes.

The script does not decide when to deploy; the operator does, and the workflow does on every push to master. It does not make a red gate green: a failed check stops before the push. It does not serve the site itself; GitHub's own "pages build and deployment" does, and that step is GitHub's.

Why it exists

Two deploy paths, one for CI and one for a person, would be two places for the gate to drift and no way to tell from gh-pages where a page came from. test/cases/96_site_deploy.sh clones the checkout, gives it a local bare remote, builds the site, and proves every refusal pushes nothing, the first deploy creates gh-pages with the site, .nojekyll and the source commit in the message, the redeploy of unchanged output pushes nothing, and a site/public built from another commit is refused.

Detail rendered from docs/claims/site-deploy-one-path.md.

Provenance

defined in
read it on this site · docs/GITHUB_PAGES_ARCHITECTURE.md
implemented in
scripts/site-deploy
proved by
test/cases/96_site_deploy.sh
claim id
site-deploy-one-path

Verify it yourself

The test runs in a disposable temporary repository and asserts the behaviour, not a string in the source.

from a clone of the repository
bash test/run.sh 96_site_deploy