Deploy docs by commit instead of by release tag
What was wrong
deploy-docs-stg.yml triggered on push: tags: v*, so the docs site could only ship when the library shipped. A typo fix, a new gallery example, or a corrected benchmark table waited for the next version tag, and a slipped release took the docs with it. Three further things fell out of that design:
- Production promotion lived in the same run, gated on the GitHub Release and the PyPI upload appearing (
verify-library-release). - A pending production approval blocked staging.
await-prod-approvalhastimeout-minutes: 720and sits inside the workflow-levelconcurrencygroup withcancel-in-progress: false, so any later staging deploy queued behind it for up to 12 hours. Rare at release cadence; unworkable at commit cadence. verify-library-releasecould never report its own failure. 30 attempts × 60s of sleeping exceeded the job'stimeout-minutes: 30, so the job was killed a beat before printing the explicit "refusing to promote" error.
Separately, scripts/check_release_version.py and its test claimed the docs site deploys on CalVer 2026.WW.N tags. Nothing has ever done that — the workflow deployed on v*.
What this does
Staging follows commits (deploy-docs-stg.yml, rewritten). Two entry points, one sha-<short7> tag scheme shared with the dev deploy:
workflow_runafter a successful dev deploy — staging promotes the images that run already built for the commit, skipping the build job entirely rather than producing a second copy of bytes that already exist;workflow_dispatchwith aref— any branch, tag, or commit on demand, which does build, since nothing may have built that ref yet. This is also how an unmerged branch gets previewed on staging.
Production promotion moves to its own workflow (deploy-docs-prod.yml, new). It promotes the tag staging currently runs, read out of the internal chart's values-stg.yaml — preserving the "prod gets exactly what a human looked at on staging" guarantee the old single-run chain got for free — and refuses to act if that file's frontend and backend tags disagree, since a split staging state has no single thing to promote. An explicit image_tag input is the rollback path: any tag still in the registry ships without a rebuild or a re-cut release. The production environment approval is unchanged. The separate concurrency group is what un-blocks staging.
The release coupling becomes information, not a gate. Decoupling means production docs can describe code that is not on PyPI yet, so the consequence is surfaced where the decision is made: resolve computes the nearest reachable v* tag, how far ahead of it the promoted commit is, and that version's PyPI status, and writes them to the run summary before the approval gate. An unreachable PyPI reports unknown rather than unpublished — a wrong claim there is worse than an honest one. Blocking on any of it would restore the coupling this PR removes.
Verification
Workflow YAML parses; every run: block passes bash -n; both resolve scripts were executed against stubbed gh/yq and a scratch git repo with real tags:
- staging: promotion path, dispatch on a branch/tag, and rejection of
../../etc,refs/heads/../x,-rf,a b, a 129-char ref, an empty ref, an unresolvable ref, and a malformed upstream SHA; - production: default-from-staging, explicit rollback tag, legacy release-tag shape, split staging state, missing tags, an injection-shaped tag, and an overlong tag;
- release context: exact-tag, N-commits-past-tag, and untagged-history cases.
make check-ci, ruff check, ruff format --check, and pytest tests/test_check_release_version.py all pass. The docs-app-codespell pre-commit hook fails in this sandbox (docs/app venv cannot be provisioned, codespell binary missing) — it fails identically on a clean checkout and only matches ^docs/, which this PR does not touch.
Notes for review
workflow_runtriggers only fire once the workflow file is on the default branch, so the automatic staging promotion starts working after this merges;workflow_dispatchworks immediately.- Staging now tracks
mainrather than release candidates. That is the intended shape of the change, but it is the one behavioral judgment call here worth a second opinion. - New spec:
spec/process/docs-deploy.md, linked fromspec/README.md, documenting the ladder, the trigger model, build-once-promote-many, rollback, and the required repository variables/secrets.
Generated by Claude Code
Summary by CodeRabbit
-
New Features
- Added independent development, staging, and production documentation deployment workflows.
- Supports commit-based staging deployments, tag-based production promotion, and production rollback without rebuilding.
- Added approval controls and deployment summaries for production releases.
-
Documentation
- Documented the documentation deployment process and promotion workflow.
- Clarified accepted release tag formats and deployment behavior.
-
Tests
- Clarified coverage for non-release CalVer tags.