diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2ddd5b8..80bceb6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -46,16 +46,37 @@ jobs: # A green deploy is not proof the site serves what we built: #110 shipped a "successful" # deploy whose artifact silently omitted .well-known/, and only a manual curl caught it. # Fail the run instead, on the canonical host the Worker actually fronts. - - name: smoke — the canonical URLs answer + # Split by what each half can prove (#112). Cloudflare bot protection 403s the runner's + # datacenter IP on ordinary paths but exempts /.well-known/ by design (so ACME and + # security.txt keep working), so the canonical host is only probeable there. + - name: smoke — the deployed artifact is complete (origin) run: | set -uo pipefail fail=0 - for path in / /feed.xml /.well-known/security.txt /.well-known/pgp-security.asc; do - # -f makes a 4xx an error so --retry-all-errors keeps trying: the Worker caches its - # own 404 for 60s, so the first probe after a deploy can legitimately still miss. - code=$(curl -fsS -L -o /dev/null -w '%{http_code}' \ - --retry 10 --retry-delay 10 --retry-all-errors \ - "https://material-identity.eu${path}") || { code="${code:-000} FAILED"; fail=1; } - printf '%-34s %s\n' "$path" "$code" + # No bot protection here, so this half covers every path — it is the #110 class + # (a directory silently missing from the artifact), generalized beyond two files. + for path in / /feed.xml /well-known/security.txt /well-known/pgp-security.asc; do + code=$(curl -fsS -L -o /dev/null -w '%{http_code}' --retry 5 --retry-delay 10 \ + --retry-all-errors "https://material-identity.github.io/dictionary${path}") \ + || { code="${code:-000} FAILED"; fail=1; } + printf '%-32s %s\n' "$path" "$code" done exit $fail + + - name: smoke — the canonical URLs answer with the right types (worker) + run: | + set -uo pipefail + fail=0 + # A 200 with the wrong type is still a regression: the content types ARE the + # requirement from #107/#109. -f makes a 4xx an error so --retry-all-errors keeps + # trying; the Worker caches its own 404 for 60s, so the first probe can legitimately miss. + check() { + local path=$1 want=$2 got + got=$(curl -fsS -L -o /dev/null -w '%{content_type}' --retry 10 --retry-delay 10 \ + --retry-all-errors "https://material-identity.eu${path}") || got="unreachable" + printf '%-32s %s\n' "$path" "$got" + [ "$got" = "$want" ] || { echo " expected: $want"; fail=1; } + } + check /.well-known/security.txt 'text/plain; charset=utf-8' + check /.well-known/pgp-security.asc 'application/pgp-keys' + exit $fail diff --git a/CLAUDE.md b/CLAUDE.md index dd53d9b..a50e2e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,7 +119,10 @@ section for the full explanation). not under `published/`. **Nothing emitted into `site/` may start with a dot**: `upload-pages-artifact` tars with `--exclude=.[^/]*`, so the build writes `site/well-known/` and the Worker rewrites `/.well-known/` onto it (#110). A build test asserts the site is - dot-free, and `deploy.yml` smoke-tests the live URLs — a green deploy alone proved nothing + dot-free, and `deploy.yml` smoke-tests after deploying — a green deploy alone proved nothing. + Two probes, because Cloudflare 403s the runner on ordinary paths but exempts `/.well-known/` + (#112): the origin for artifact completeness, the canonical host for the Worker's rewrite and + content types. Probe the origin, not `material-identity.eu`, for anything outside `.well-known` - `REVIEW.md` — what reviewers check beyond CI; read it before reviewing any publish PR - `standards/` — local-only licensed docs; only its README is committed