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

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. Awave-approvedlabel added by the repository owner on a PR intomaindeploys the PR head underwave-w<n>. The wave comes from the PR title, which must name exactly oneW<n>. Every other label event skips the job. - Manual runs (
workflow_dispatch) refuse the reserved aliasesmain,production,pr-*,wave-*andv<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.0pinned; every event value reaches the shell throughenv:.
- Wave approval. The workflow now also runs on
tools/previews/versions_page.py:- it pages with
result_info.total_pages; - it leaves out deployments whose
latest_stage.statusis notsuccess; - it prints the API's key names, never values, to the CI log.
- it pages with
- 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
/versionsfields, whether direct uploads count as builds, and the 2026-10-27 checks; - §7.2: prebuilds, the static check, and what Thomas must check live.
- §7.1: wave approval and its residual risk, the token scope and secret names, the
- Review record:
docs/reviews/code/D5.md. Figure:tools/diagrams/d5_previews.py. - GitHub: the label
wave-approvedwas 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 withbuild 950e1de.https://pr-13.spectio.pages.dev/versions/answered 200 and listedpr-13 950e1dewith 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,/versionsand 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-approvedon 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/integrationskipped the job, as intended. Source: the Actions runs API, which lists40524120754 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.pycovers it:(W2 complete)giveswave-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.shrunsmkdocs serve -a 0.0.0.0:8000with themkdocsthatsetup.shinstalls. dev/versions.envdoes 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-previewssketch, light and dark, at the top of this page:python3 tools/diagrams/d5_previews.py.
Findings and open questions¶
- For Thomas (by hand):
- Rename the secrets in the
previewsenvironment toCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID. The fallbacks can then be removed frompreviews.yml. - Open a codespace from the PR comment's badge. Check that
setup.shfinishes, that the docs answer on port 8000 and thatmkdocs servestays up; time the start. - Look at the dashboard's build count to see whether direct uploads count as builds.
- Decide whether a
mainprebuild is worth its storage and Actions minutes. - Optionally, add a separate environment with yourself as required reviewer for the wave path (plan support [to be validated]).
- Rename the secrets in the
- 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.