Skip to content

D5 · Dev previews end to end: Cloudflare Pages and Codespaces

Done 2026-10-27 · PR #13 · two pushes deployed and checked live · wave-approved path added · Codespaces checked statically · 5 checks left to Thomas

D5 figure: a push to PR 13, or Thomas's wave-approved label, starts previews.yml. It builds the docs with the head SHA in the footer, reads the deployments API for /versions and uploads to Cloudflare Pages, where the first and second pushes keep their own hash URLs and the pr-13 alias follows the latest. /versions lists them all and the PR comment links back. Below: Codespaces on demand, checked statically, with the start time not measured. D5 figure: a push to PR 13, or Thomas's wave-approved label, starts previews.yml. It builds the docs with the head SHA in the footer, reads the deployments API for /versions and uploads to Cloudflare Pages, where the first and second pushes keep their own hash URLs and the pr-13 alias follows the latest. /versions lists them all and the PR comment links back. Below: Codespaces on demand, checked statically, with the start time not measured.

Every value in the figure comes from the two runs of previews.yml on PR #13. The Codespaces panel is a static reading of .devcontainer/, not a live start.

What changed

  • .github/workflows/previews.yml (spec §7.1):
    • Wave approval. The workflow now also runs on labeled. A wave-approved label added by the repository owner on a PR into main deploys the PR head under wave-w<n>. The wave comes from the PR title, which must name exactly one W<n>. Every other label event skips the job.
    • Manual runs (workflow_dispatch) refuse the reserved aliases main, production, pr-*, wave-* and v<digit>*.
    • The PR head is built, not GitHub's merge commit, so the footer's build <sha> names exactly what is served.
    • The verification step now also waits until the branch alias shows the new SHA.
    • The PR comment gives the build SHA, and wave previews get their own comment. Only the bot's own comment is updated.
    • Hardening: permissions: {} at the top with per-job grants; actions pinned by commit SHA, at the same majors; wrangler@4.153.0 pinned; every event value reaches the shell through env:.
  • tools/previews/versions_page.py:
    • it pages with result_info.total_pages;
    • it leaves out deployments whose latest_stage.status is not success;
    • it prints the API's key names, never values, to the CI log.
  • Tests: tools/previews/test_versions_page.py (7 tests) uses the live API shape; tools/previews/test_alias.py (5 tests) runs the workflow's own alias step.
  • .devcontainer/devcontainer.json: Node 22 → 24, to match §1.
  • Spec updates in dev-environment-and-testing.md:
    • §7.1: wave approval and its residual risk, the token scope and secret names, the /versions fields, whether direct uploads count as builds, and the 2026-10-27 checks;
    • §7.2: prebuilds, the static check, and what Thomas must check live.
  • Review record: docs/reviews/code/D5.md. Figure: tools/diagrams/d5_previews.py.
  • GitHub: the label wave-approved was created in the repository, which the new path needs.

Why

The previews deployed since 2026-10-26, but nobody had checked them end to end. Three things were still missing: a check that an earlier deployment survives a push, the shape of the deployments API, and a wave path.

Prompt: next-phase.md · D5. No new decision; the design is the 2026-10-26 entry of the decision log.

Evidence

Live checks on PR #13 (curl from the agent session, 2026-10-27):

Push Run Hash URL Alias pr-13 Footer
1 · 950e1de 40522381183 ✅ https://4367baf3.spectio.pages.dev/ 200 200 build 950e1de · preview pr-13
2 · 086a84b 40523119556 ✅ https://8c909d6d.spectio.pages.dev/ 200 200, moved to 086a84b build 086a84b · preview pr-13

After push 2:

  • https://4367baf3.spectio.pages.dev/ still answered 200 with build 950e1de.
  • https://pr-13.spectio.pages.dev/versions/ answered 200 and listed pr-13 950e1de with its link.
  • The github-actions[bot] comment was created at 14:35:05Z and updated in place at 14:39:41Z, with the build SHA, hash URL, alias, /versions and the Codespaces badge (codespaces.new/weber8thomas/spectio/tree/autopilot/D5?quickstart=1).

Deployments API shape, printed by the script in run 40522381183:

deployments API shape: result_info={'page': 1, 'per_page': 25, 'count': 25, 'total_count': 50, 'total_pages': 2}
deployment keys=['aliases', 'build_config', 'build_image_major_version', 'compatibility_date', 'compatibility_flags',
 'created_on', 'deployment_trigger', 'env_vars', 'environment', 'id', 'is_skipped', 'latest_stage', 'modified_on',
 'production_branch', 'project_id', 'project_name', 'short_id', 'stages', 'url', 'usage_model', 'uses_functions']
deployment_trigger keys=['metadata', 'type'] metadata keys=['branch', 'commit_dirty', 'commit_hash', 'commit_message']
latest_stage keys=['ended_on', 'name', 'started_on', 'status']

Every field the script reads is there. Run 40523119556 read 51 deployments over 3 pages (total_pages: 3).

Wave path, live and local:

  • Run 40522735039: wave-approved on PR #13, whose title names no wave, failed as expected. This was a deliberate negative test; the label was removed afterwards. Job log, line 238:

    2026-10-27T14:36:42.6761304Z ##[error]wave-approved needs exactly one wave id (W<n>) in the PR title, found:
    

  • Run 40524120754: the owner's label on a PR into autopilot/integration skipped the job, as intended. Source: the Actions runs API, which lists 40524120754 pull_request autopilot/D5 311d13e completed skipped; a skipped job has no log.

  • The positive path was not run live: that would publish an unapproved wave preview. test_alias.py covers it: (W2 complete) gives wave-w2, and two different ids are refused.

Secrets: a one-off check in run 40522381183 showed that CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are not set. The deploys use the misspelt fallbacks (CLOUDFARE_*), so they stay until Thomas renames the secrets.

Tests:

$ python3 -m unittest discover -s tools/previews -p 'test_*.py'
Ran 12 tests in 0.228s
OK

Action pins, from git ls-remote --tags on 2026-10-27: checkout v4.4.0 11d5960a…, setup-uv v6.8.0 d0cc045d…, github-script v7.1.0 f28e40c7….

Do direct uploads count as builds? The Cloudflare docs do not say (checked 2026-10-27). The limits page ties builds to Git pushes, and the Direct Upload page gives no build quota. This stays [to be validated] on the dashboard; see §7.1.

Codespaces, read statically, since a codespace cannot be opened from here. Running the base image in Docker was refused by the session's sandbox.

  • Port 8000 is forwarded, and start.sh runs mkdocs serve -a 0.0.0.0:8000 with the mkdocs that setup.sh installs.
  • dev/versions.env does not exist yet (D3 writes it), so the defaults apply: Nextflow 26.10.3 and Snakemake 9.27.1, matching §1.
  • Node was 22 against Node 24 in §1, and is now fixed. The details are in §7.2.

review-code:

CODE  step=D5  review=approve b/M/m=0/0/2  security=approve b/M/m=0/0/2  rounds=2  simplify=nochange -0/+0  tests=pass
  • code-reviewer: changes (0/1/4), then approve (0/0/2).
  • security-reviewer: changes (0/1/3), then approve (0/0/2).
  • Two rounds; code-simplifier: no change, tests pass.
  • Findings: reviews/code/D5.md.

Figures and media

  • d5-previews sketch, light and dark, at the top of this page: python3 tools/diagrams/d5_previews.py.

Findings and open questions

  • For Thomas (by hand):
    1. Rename the secrets in the previews environment to CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID. The fallbacks can then be removed from previews.yml.
    2. Open a codespace from the PR comment's badge. Check that setup.sh finishes, that the docs answer on port 8000 and that mkdocs serve stays up; time the start.
    3. Look at the dashboard's build count to see whether direct uploads count as builds.
    4. Decide whether a main prebuild is worth its storage and Actions minutes.
    5. Optionally, add a separate environment with yourself as required reviewer for the wave path (plan support [to be validated]).
  • Rule: only Thomas adds wave-approved; agents never do (§7.1). The code cannot tell the two apart.
  • For D4 or Renovate: a lockfile for wrangler, installed with npm ci.
  • For D3: pin the dev-container image, the features, uv, mkdocs, mkdocs-material and Playwright in dev/versions.env.