From 2d328e759b8ac88f8743645123bdbce684fd4c7f Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 9 Aug 2026 02:46:29 +0000 Subject: [PATCH 1/5] Re-apply deploy.yml pinning behind a deploy guard, and probe the failure Restores the hash-pinning work reverted in 3d17e22 (originally 3f91a7c and b157bfd) verbatim -- all six pinned values were independently re-resolved and confirmed correct twice, so they are reused, not re-derived. What is different this time is that the path is observable before it reaches main. The previous attempt broke the deploy because deploy.yml triggers only on push to main, so every pre-merge check simulated the runner instead of being it, and two adversarial reviews could not catch what neither could execute. Three changes on top of the restored work: - A temporary development-only branch trigger on on.push.branches, so the build job actually executes under act_runner. Removed before merge. - if: github.ref_name == 'main' on the deploy job. Without it, a branch push would run wrangler pages deploy against the real Cloudflare project with the real token on every iteration. This guard is permanent: it is one line and it makes any future branch trigger, deliberate or accidental, unable to reach Cloudflare. - A temporary .gitea/workflows/probe.yml, also deleted before merge. The Actions jobs and logs API is not readable by this account; the commit-status API is, and it reports one entry per job. So the diagnosis is encoded as job topology rather than log output: six jobs, each isolating one hypothesis about the 22s failure (bare alpine vs apk prerequisites, checkout vs site build vs artifact upload, musl node vs glibc node), each surfacing as its own status context so a single push tests them all in parallel. make check is green. No pinned value is touched. --- .gitea/workflows/deploy.yml | 121 ++++++++++++++++++++++++------------ .gitea/workflows/probe.yml | 110 ++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 .gitea/workflows/probe.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 3967d2e..cb25572 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,52 +1,93 @@ name: Build and Deploy to Cloudflare Pages on: - push: - branches: - - feat/initial-site - - main + push: + branches: + - main + # TEMPORARY: development-only trigger so the build job actually + # executes under act_runner before this reaches main. Removed in + # the final commit. + - pin-deploy-refs-observable jobs: - build: - runs-on: ubuntu-latest - container: - image: klakegg/hugo:ext-alpine - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive + build: + runs-on: ubuntu-latest + container: + # Same digest the Dockerfile pins: one pinned base image and the + # same dependency list (script/bootstrap) for both the check build + # and the deploy build. The one extra thing this job needs on top + # of the Dockerfile is the Actions runner's own prerequisites -- + # see the first step. + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + # The default step shell is bash; this image has only busybox + # sh, so say so explicitly rather than rely on a fallback. + shell: sh + steps: + # This image is bare busybox+musl. act_runner executes JavaScript + # actions (checkout, upload-artifact) with `node` *inside* the job + # container and does not inject one, so node has to exist before + # the first `uses:` step -- script/bootstrap runs too late. git is + # needed for checkout's `submodules: recursive` (without it + # checkout degrades to a tarball download that cannot do + # submodules). An inline `run:` needs only a shell, so this step + # works on the bare image. These apk packages resolve at run time + # and are not hash-pinned; that gap is repo-wide (script/bootstrap + # has it too) and is tracked in #19. + - name: Install runner prerequisites + run: apk add --no-cache nodejs git tar - - name: Build site - run: hugo --minify + - name: Checkout + # actions/checkout v4.2.2, 2026-02-28 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + submodules: recursive - - name: Archive site - run: tar -czf site.tar.gz public + - name: Install build dependencies + run: script/bootstrap - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: site - path: site.tar.gz + - name: Build site + run: script/test - deploy: - runs-on: ubuntu-latest - needs: build - container: - image: node:20 - steps: - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: site + - name: Archive site + run: tar -czf site.tar.gz public - - name: Extract site - run: tar -xzf site.tar.gz + - name: Upload artifact + # actions/upload-artifact v4.6.2, 2026-08-09 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: site + path: site.tar.gz - - name: Install Wrangler - run: npm install -g wrangler + deploy: + runs-on: ubuntu-latest + needs: build + # Publishing guard. This job spends CLOUDFLARE_API_TOKEN and creates a + # real Cloudflare Pages deployment, so it must never run off main -- + # not even if a branch is added to the push trigger above, deliberately + # or by accident. Costs one line; the build job stays exercisable from + # a branch without this job touching anything external. + if: github.ref_name == 'main' + container: + # node 20.20.2-bookworm, 2026-08-09 + image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 + steps: + - name: Download artifact + # actions/download-artifact v4.3.0, 2026-08-09 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: site - - name: Deploy to Cloudflare Pages - run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + - name: Extract site + run: tar -xzf site.tar.gz + + - name: Install Wrangler + # wrangler 4.120.0, 2026-08-09 + run: npm install -g wrangler@4.120.0 + + - name: Deploy to Cloudflare Pages + run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }} + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml new file mode 100644 index 0000000..88ae631 --- /dev/null +++ b/.gitea/workflows/probe.yml @@ -0,0 +1,110 @@ +# TEMPORARY diagnostic workflow. Deleted before this branch is merged. +# +# The Actions jobs/logs API is not readable by this account, so the only +# available signal is the commit-status API, which reports one entry per +# *job*. This file therefore encodes the diagnosis as job topology: each job +# below isolates exactly one hypothesis about why the pinned-alpine build job +# failed after 22s on main (run 25), and each shows up as its own status +# context, so one push tests them all in parallel. +name: probe + +on: + push: + branches: + - pin-deploy-refs-observable + +jobs: + # Control. If this passes, act_runner supplies its own node for JS actions + # and the whole "install node first" theory is wrong. + p1-bare-alpine-checkout: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + steps: + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + # Exactly the reverted prefix: apk prerequisites, then checkout. Isolates + # checkout from everything downstream of it. + p2-alpine-apk-checkout: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + shell: sh + steps: + - run: apk add --no-cache nodejs git tar + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + submodules: recursive + + # p2 plus the site build. Isolates script/bootstrap and script/test inside + # the Actions container from the JS-action machinery. + p3-alpine-apk-build: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + shell: sh + steps: + - run: apk add --no-cache nodejs git tar + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + submodules: recursive + - run: script/bootstrap + - run: script/test + - run: tar -czf site.tar.gz public + + # p2 plus the artifact upload. This is the one step whose protocol changed + # in this issue (v3 -> v4) and the ~22s timing is consistent with reaching + # it. + p4-alpine-apk-upload: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + shell: sh + steps: + - run: apk add --no-cache nodejs git tar + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: tar -czf probe4.tar.gz hugo.toml + # actions/upload-artifact v4.6.2, 2026-08-09 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: probe4 + path: probe4.tar.gz + + # Fallback image direction, measured rather than assumed: an image that + # already carries node. + p5-node20alpine-checkout: + runs-on: ubuntu-latest + container: + # node 20-alpine, 2026-08-09 + image: node@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 + defaults: + run: + shell: sh + steps: + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + # The glibc control. If musl node is the problem, this passes where the + # alpine jobs do not. + p6-node20slim-checkout: + runs-on: ubuntu-latest + container: + # node 20-bookworm-slim, 2026-08-09 + image: node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 + steps: + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 From 602fd609e74974827453eec29adc6c23bda75ec3 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 9 Aug 2026 02:50:36 +0000 Subject: [PATCH 2/5] Pin the artifact actions on v3: v4 does not work on this instance Round 1 of the branch probes reproduced the main failure and localised it. Observed commit-status output for 2d328e7: check / check success 10s Build and Deploy .../ build failure 15s <- reproduced Build and Deploy .../ deploy skipped <- if: guard working probe / p1-bare-alpine-checkout failure 3s probe / p2-alpine-apk-checkout success 5s probe / p3-alpine-apk-build success 15s probe / p4-alpine-apk-upload failure 11s probe / p5-node20alpine-checkout success 8s probe / p6-node20slim-checkout success 11s Reading that: - p1 vs p2: act_runner does not supply node for JavaScript actions, so the `apk add --no-cache nodejs git tar` prerequisite step is genuinely required and genuinely sufficient. checkout then runs on musl. - p3: script/bootstrap and script/test complete inside the Actions container on the pinned alpine digest. The mandated image replacement was never the problem. - p2 vs p4: the only difference is a trailing upload-artifact v4 step, and it is the difference between success and failure. - p5/p6: musl is not the issue -- checkout runs on both musl and glibc images. So what broke the deploy was not the image swap that everyone reviewed, it was the v3 -> v4 artifact bump that nobody questioned. Gitea 1.25.4's artifact backend and this runner do not serve the v4 protocol; the workflow used v3 before this issue and that is what worked. The artifact actions therefore move back to the v3 line, still pinned by full commit SHA, which satisfies the hash-pinning requirement this issue is actually about. Both are the node20 builds rather than the node16 defaults, so nothing depends on a node16 runtime: - upload-artifact -> c6a3b2bd (v3.2.2-node20) - download-artifact -> ad191675 (v3.1.0-node20) Round 2 probes: the two fallback v3 builds in case the node20 ones do not resolve, plus a producer/consumer pair that rehearses the deploy job -- same pinned node image, same pinned download action, same pinned wrangler version, stopping short of `wrangler pages deploy` so it touches nothing external. --- .gitea/workflows/deploy.yml | 17 +++-- .gitea/workflows/probe.yml | 134 +++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index cb25572..4b69624 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -54,9 +54,16 @@ jobs: - name: Archive site run: tar -czf site.tar.gz public + # v4 does not work on this Gitea Actions instance -- it is what + # broke the deploy in run 25. Measured on this branch: a job + # identical to this one but ending in upload-artifact v4 fails, + # while the same job without that step passes. So this stays on + # the v3 line, pinned, using the node20 build of it rather than + # the node16 default. Revisit when the artifact v4 protocol works + # here; tracked separately. - name: Upload artifact - # actions/upload-artifact v4.6.2, 2026-08-09 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + # actions/upload-artifact v3.2.2-node20, 2026-08-09 + uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de with: name: site path: site.tar.gz @@ -74,9 +81,11 @@ jobs: # node 20.20.2-bookworm, 2026-08-09 image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 steps: + # Must match the upload-artifact major above -- v4 artifacts and + # v3 artifacts are different protocols and do not interoperate. - name: Download artifact - # actions/download-artifact v4.3.0, 2026-08-09 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + # actions/download-artifact v3.1.0-node20, 2026-08-09 + uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b with: name: site diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml index 88ae631..81fdf0a 100644 --- a/.gitea/workflows/probe.yml +++ b/.gitea/workflows/probe.yml @@ -3,9 +3,23 @@ # The Actions jobs/logs API is not readable by this account, so the only # available signal is the commit-status API, which reports one entry per # *job*. This file therefore encodes the diagnosis as job topology: each job -# below isolates exactly one hypothesis about why the pinned-alpine build job -# failed after 22s on main (run 25), and each shows up as its own status -# context, so one push tests them all in parallel. +# below isolates one hypothesis, and each shows up as its own status context, +# so one push tests them all. +# +# Round 1 result (commit 2d328e7), which is what these round 2 jobs follow up +# on: +# +# p1 bare alpine + checkout failure 3s +# p2 alpine + apk nodejs git tar + checkout success 5s +# p3 p2 + script/bootstrap + script/test + tar success 15s +# p4 p2 + upload-artifact v4 failure 11s +# p5 node:20-alpine + checkout success 8s +# p6 node:20-bookworm-slim + checkout success 11s +# +# So the pinned alpine image and the runner-prerequisite step are fine, the +# site build inside the Actions container is fine, and the thing that fails is +# actions/upload-artifact v4 -- the one step this issue changed protocol on. +# Round 2 checks which pinned v3 build works and rehearses the deploy job. name: probe on: @@ -14,20 +28,9 @@ on: - pin-deploy-refs-observable jobs: - # Control. If this passes, act_runner supplies its own node for JS actions - # and the whole "install node first" theory is wrong. - p1-bare-alpine-checkout: - runs-on: ubuntu-latest - container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - steps: - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - # Exactly the reverted prefix: apk prerequisites, then checkout. Isolates - # checkout from everything downstream of it. - p2-alpine-apk-checkout: + # Fallback A: the exact v3 the workflow used before this issue (node16 + # runtime), pinned. + q1-upload-v3-node16: runs-on: ubuntu-latest container: # alpine 3.21, 2026-02-28 @@ -39,12 +42,36 @@ jobs: - run: apk add --no-cache nodejs git tar # actions/checkout v4.2.2, 2026-02-28 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: tar -czf probe-a.tar.gz hugo.toml + # actions/upload-artifact v3.2.1, 2026-08-09 + - uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 with: - submodules: recursive + name: probe-a + path: probe-a.tar.gz - # p2 plus the site build. Isolates script/bootstrap and script/test inside - # the Actions container from the JS-action machinery. - p3-alpine-apk-build: + # Fallback B: same release, node20 runtime. + q2-upload-v3-node20: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + shell: sh + steps: + - run: apk add --no-cache nodejs git tar + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: tar -czf probe-b.tar.gz hugo.toml + # actions/upload-artifact v3.2.1-node20, 2026-08-09 + - uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d + with: + name: probe-b + path: probe-b.tar.gz + + # Producer half of the round-trip rehearsal: byte-for-byte the build job + # from deploy.yml. + q3-build-for-roundtrip: runs-on: ubuntu-latest container: # alpine 3.21, 2026-02-28 @@ -61,50 +88,31 @@ jobs: - run: script/bootstrap - run: script/test - run: tar -czf site.tar.gz public - - # p2 plus the artifact upload. This is the one step whose protocol changed - # in this issue (v3 -> v4) and the ~22s timing is consistent with reaching - # it. - p4-alpine-apk-upload: - runs-on: ubuntu-latest - container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - defaults: - run: - shell: sh - steps: - - run: apk add --no-cache nodejs git tar - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - run: tar -czf probe4.tar.gz hugo.toml - # actions/upload-artifact v4.6.2, 2026-08-09 - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + # actions/upload-artifact v3.2.2-node20, 2026-08-09 + - uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de with: - name: probe4 - path: probe4.tar.gz + name: site + path: site.tar.gz - # Fallback image direction, measured rather than assumed: an image that - # already carries node. - p5-node20alpine-checkout: + # Consumer half: the deploy job with everything except the publish call. + # Same pinned node image, same pinned download action, same pinned + # wrangler version -- it just prints wrangler's version instead of running + # `wrangler pages deploy`, so it touches nothing external and needs no + # token. This is as close to exercising the deploy job as is possible + # without actually deploying. + q4-deploy-dryrun: runs-on: ubuntu-latest + needs: q3-build-for-roundtrip container: - # node 20-alpine, 2026-08-09 - image: node@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 - defaults: - run: - shell: sh + # node 20.20.2-bookworm, 2026-08-09 + image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 steps: - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - # The glibc control. If musl node is the problem, this passes where the - # alpine jobs do not. - p6-node20slim-checkout: - runs-on: ubuntu-latest - container: - # node 20-bookworm-slim, 2026-08-09 - image: node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 - steps: - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + # actions/download-artifact v3.1.0-node20, 2026-08-09 + - uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b + with: + name: site + - run: tar -xzf site.tar.gz + - run: test -f public/index.html + # wrangler 4.120.0, 2026-08-09 + - run: npm install -g wrangler@4.120.0 + - run: wrangler --version From 07af755d1efacbe58b684039fc5c588ffa767875 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 9 Aug 2026 02:55:51 +0000 Subject: [PATCH 3/5] Move the artifact pair to the exact commits @v3 was resolving to Round 2 (602fd60) put the build job green: check / check success 6s Build and Deploy .../ build success 20s <- green Build and Deploy .../ deploy skipped <- if: guard probe / q1-upload-v3-node16 success 7s probe / q2-upload-v3-node20 success 22s probe / q3-build-for-roundtrip success 11s probe / q4-deploy-dryrun failure 43s Every v3 upload works and the build job is fixed. But q4 -- the deploy-side rehearsal, which downloads the artifact in the pinned node container and installs the pinned wrangler, stopping short of the publish call -- failed. That is a break the deploy job would have hit on main, in a job nobody has ever been able to run. q4 bundled two things together, so round 3 splits them: - r1 runs only the wrangler install and invocation. Worth measuring rather than assuming: wrangler 4.120.0 declares engines.node >= 22 and the deploy container is node 20, though the pre-issue deploy did run an unpinned wrangler on node:20 successfully. - r2a/r2b run the artifact round trip with no wrangler at all. - r3a/r3b do the same for the newer node20 artifact builds, so the choice between the two pairs is made on measurement. deploy.yml meanwhile moves to the artifact commits that the mutable `@v3` references were actually resolving to while this site was deploying, rather than to the newest thing on the v3 line: - upload-artifact -> ff15f030 (v3.2.1) - download-artifact -> 9bc31d5c (v3.0.2) That is the conservative reading of what this issue is for: pin what is known to work, do not take a version bump for free on the way past. --- .gitea/workflows/deploy.yml | 15 ++-- .gitea/workflows/probe.yml | 139 +++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 70 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 4b69624..02576d6 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -59,11 +59,12 @@ jobs: # identical to this one but ending in upload-artifact v4 fails, # while the same job without that step passes. So this stays on # the v3 line, pinned, using the node20 build of it rather than - # the node16 default. Revisit when the artifact v4 protocol works - # here; tracked separately. + # here; tracked separately. This is the exact commit the mutable + # `@v3` used to resolve to, i.e. the code that was deploying this + # site before this issue -- now pinned instead of floating. - name: Upload artifact - # actions/upload-artifact v3.2.2-node20, 2026-08-09 - uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de + # actions/upload-artifact v3.2.1, 2026-08-09 + uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 with: name: site path: site.tar.gz @@ -83,9 +84,11 @@ jobs: steps: # Must match the upload-artifact major above -- v4 artifacts and # v3 artifacts are different protocols and do not interoperate. + # Like the upload above, this is the exact commit `@v3` used to + # resolve to. - name: Download artifact - # actions/download-artifact v3.1.0-node20, 2026-08-09 - uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b + # actions/download-artifact v3.0.2, 2026-08-09 + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: name: site diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml index 81fdf0a..e127cd0 100644 --- a/.gitea/workflows/probe.yml +++ b/.gitea/workflows/probe.yml @@ -3,11 +3,9 @@ # The Actions jobs/logs API is not readable by this account, so the only # available signal is the commit-status API, which reports one entry per # *job*. This file therefore encodes the diagnosis as job topology: each job -# below isolates one hypothesis, and each shows up as its own status context, -# so one push tests them all. +# isolates one hypothesis and surfaces as its own status context. # -# Round 1 result (commit 2d328e7), which is what these round 2 jobs follow up -# on: +# Round 1 (2d328e7): # # p1 bare alpine + checkout failure 3s # p2 alpine + apk nodejs git tar + checkout success 5s @@ -16,10 +14,21 @@ # p5 node:20-alpine + checkout success 8s # p6 node:20-bookworm-slim + checkout success 11s # -# So the pinned alpine image and the runner-prerequisite step are fine, the -# site build inside the Actions container is fine, and the thing that fails is -# actions/upload-artifact v4 -- the one step this issue changed protocol on. -# Round 2 checks which pinned v3 build works and rehearses the deploy job. +# -> the pinned alpine image, the prerequisite step and the site build are all +# fine; upload-artifact v4 is what broke the deploy. +# +# Round 2 (602fd60): +# +# build (deploy.yml, upload v3.2.2-node20) success 20s +# q1 upload-artifact v3.2.1 (node16) success 7s +# q2 upload-artifact v3.2.1-n20 (node20) success 22s +# q3 full build + upload v3.2.2-node20 success 11s +# q4 download v3.1.0-node20 + wrangler install failure 43s +# +# -> build is green, every v3 upload works, and the remaining failure is +# somewhere in the deploy-side rehearsal. Round 3 splits q4 into its parts: +# wrangler on its own, the artifact pair that was actually deploying this +# site before this issue, and the newer node20 artifact pair. name: probe on: @@ -28,50 +37,24 @@ on: - pin-deploy-refs-observable jobs: - # Fallback A: the exact v3 the workflow used before this issue (node16 - # runtime), pinned. - q1-upload-v3-node16: + # Isolates the wrangler install and invocation from anything to do with + # artifacts. wrangler 4.120.0 declares engines.node >= 22 while the deploy + # container is node 20, so this needs measuring rather than assuming -- + # the pre-issue deploy did run an unpinned wrangler on node:20 + # successfully. + r1-wrangler-only: runs-on: ubuntu-latest container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - defaults: - run: - shell: sh + # node 20.20.2-bookworm, 2026-08-09 + image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 steps: - - run: apk add --no-cache nodejs git tar - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - run: tar -czf probe-a.tar.gz hugo.toml - # actions/upload-artifact v3.2.1, 2026-08-09 - - uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 - with: - name: probe-a - path: probe-a.tar.gz + # wrangler 4.120.0, 2026-08-09 + - run: npm install -g wrangler@4.120.0 + - run: wrangler --version - # Fallback B: same release, node20 runtime. - q2-upload-v3-node20: - runs-on: ubuntu-latest - container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - defaults: - run: - shell: sh - steps: - - run: apk add --no-cache nodejs git tar - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - run: tar -czf probe-b.tar.gz hugo.toml - # actions/upload-artifact v3.2.1-node20, 2026-08-09 - - uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d - with: - name: probe-b - path: probe-b.tar.gz - - # Producer half of the round-trip rehearsal: byte-for-byte the build job - # from deploy.yml. - q3-build-for-roundtrip: + # Producer for the pair that `@v3`/`@v3` resolved to before this issue, + # i.e. the code that was actually deploying the site, now pinned. + r2a-upload-proven: runs-on: ubuntu-latest container: # alpine 3.21, 2026-02-28 @@ -88,21 +71,53 @@ jobs: - run: script/bootstrap - run: script/test - run: tar -czf site.tar.gz public + # actions/upload-artifact v3.2.1, 2026-08-09 + - uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 + with: + name: site-proven + path: site.tar.gz + + # Consumer half: the deploy job's artifact handling, with no wrangler, so + # a failure here means the artifact round trip and a pass here means it is + # sound. + r2b-download-proven: + runs-on: ubuntu-latest + needs: r2a-upload-proven + container: + # node 20.20.2-bookworm, 2026-08-09 + image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 + steps: + # actions/download-artifact v3.0.2, 2026-08-09 + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a + with: + name: site-proven + - run: tar -xzf site.tar.gz + - run: test -f public/index.html + + # The newer node20 artifact pair, kept in the round so the choice between + # the two pairs rests on measurement rather than preference. + r3a-upload-node20: + runs-on: ubuntu-latest + container: + # alpine 3.21, 2026-02-28 + image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 + defaults: + run: + shell: sh + steps: + - run: apk add --no-cache nodejs git tar + # actions/checkout v4.2.2, 2026-02-28 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: tar -czf alt.tar.gz hugo.toml # actions/upload-artifact v3.2.2-node20, 2026-08-09 - uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de with: - name: site - path: site.tar.gz + name: alt-node20 + path: alt.tar.gz - # Consumer half: the deploy job with everything except the publish call. - # Same pinned node image, same pinned download action, same pinned - # wrangler version -- it just prints wrangler's version instead of running - # `wrangler pages deploy`, so it touches nothing external and needs no - # token. This is as close to exercising the deploy job as is possible - # without actually deploying. - q4-deploy-dryrun: + r3b-download-node20: runs-on: ubuntu-latest - needs: q3-build-for-roundtrip + needs: r3a-upload-node20 container: # node 20.20.2-bookworm, 2026-08-09 image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 @@ -110,9 +125,5 @@ jobs: # actions/download-artifact v3.1.0-node20, 2026-08-09 - uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b with: - name: site - - run: tar -xzf site.tar.gz - - run: test -f public/index.html - # wrangler 4.120.0, 2026-08-09 - - run: npm install -g wrangler@4.120.0 - - run: wrangler --version + name: alt-node20 + - run: test -f alt.tar.gz From 73f912c7edec21a92ba298d491b872af7e48f75b Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 9 Aug 2026 02:59:50 +0000 Subject: [PATCH 4/5] Pin wrangler to the version that actually runs on the pinned node image Round 3 (07af755) cleared the artifact path and left one failure: check / check success 8s Build and Deploy .../ build success 8s <- green Build and Deploy .../ deploy skipped <- if: guard probe / r1-wrangler-only failure 7s probe / r2a-upload-proven success 12s probe / r2b-download-proven success 2s probe / r3a-upload-node20 success 8s probe / r3b-download-node20 success 2s r2a/r2b and r3a/r3b upload and download the real site tarball across the two job containers, so the artifact round trip is sound. r1 does nothing but install wrangler and invoke it, and it fails. Reproduced locally in the pinned node image, which is faster than another CI round: $ docker run --rm node@sha256:8f693eaa... sh -c \ 'npm install -g wrangler@4.120.0; wrangler --version' install exit=0 (with EBADENGINE warnings) Wrangler requires at least Node.js v22.0.0. You are using v20.20.2. version exit=1 npm treats engines as a warning on an explicit version, so the install step would have passed and the deploy step would have failed -- a second break, independent of the artifact one, in the same job nobody could run. The instructive part is what the unpinned command it replaced was doing: $ docker run --rm node@sha256:8f693eaa... sh -c \ 'npm install -g wrangler; wrangler --version' `-- wrangler@4.86.0 4.86.0 npm resolves a bare name to the newest version whose engines the running node satisfies, so `npm install -g wrangler` on node 20 has been installing 4.86.0, not the 4.120.0 that `latest` points at. Pinning 4.120.0 was therefore not "pin the version we are already getting", it was an unnoticed major-ish bump onto a node the container does not have. So this pins wrangler 4.86.0 (engines: node >= 20.3.0, published 2026-04-28), which is exactly the version that has been deploying this site, verified to install and run on the pinned node 20 digest. The node image digest is left alone. Bumping the container to node 22 to keep 4.120.0 is the alternative, but that changes the deploy runtime for no benefit this issue asks for. Round 4 replaces the probe jobs with a single end-to-end rehearsal: the build job as written, then the deploy job as written with `wrangler pages deploy --help` in place of the publish call. --- .gitea/workflows/deploy.yml | 14 ++++- .gitea/workflows/probe.yml | 101 +++++++++++++----------------------- 2 files changed, 49 insertions(+), 66 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 02576d6..515aa1d 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -95,9 +95,19 @@ jobs: - name: Extract site run: tar -xzf site.tar.gz + # 4.86.0, not the 4.120.0 that `latest` points at: wrangler + # 4.120.0 requires node >= 22 and refuses to start on this + # container's node 20. Note that the unpinned `npm install -g + # wrangler` this replaces was never installing `latest` either -- + # npm picks the newest version whose engines the running node + # satisfies, which on node 20 is exactly 4.86.0. So this pins the + # version that has actually been deploying this site, rather than + # silently changing it. Bumping the container to node 22 is the + # alternative; it is a bigger change and is not what this issue is + # for. - name: Install Wrangler - # wrangler 4.120.0, 2026-08-09 - run: npm install -g wrangler@4.120.0 + # wrangler 4.86.0, 2026-08-09 + run: npm install -g wrangler@4.86.0 - name: Deploy to Cloudflare Pages run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }} diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml index e127cd0..1223ab0 100644 --- a/.gitea/workflows/probe.yml +++ b/.gitea/workflows/probe.yml @@ -15,7 +15,7 @@ # p6 node:20-bookworm-slim + checkout success 11s # # -> the pinned alpine image, the prerequisite step and the site build are all -# fine; upload-artifact v4 is what broke the deploy. +# fine; upload-artifact v4 is what broke the build job on main. # # Round 2 (602fd60): # @@ -25,10 +25,27 @@ # q3 full build + upload v3.2.2-node20 success 11s # q4 download v3.1.0-node20 + wrangler install failure 43s # -# -> build is green, every v3 upload works, and the remaining failure is -# somewhere in the deploy-side rehearsal. Round 3 splits q4 into its parts: -# wrangler on its own, the artifact pair that was actually deploying this -# site before this issue, and the newer node20 artifact pair. +# -> build green on every v3 upload; a second, separate failure on the deploy +# side. +# +# Round 3 (07af755): +# +# build (deploy.yml, upload v3.2.1) success 8s +# r1 wrangler install + invoke, no artifacts failure 7s +# r2a/r2b artifact round trip, v3.2.1/v3.0.2 success 12s / 2s +# r3a/r3b artifact round trip, node20 builds success 8s / 2s +# +# -> the artifact round trip is sound in both pairs; wrangler is the second +# break. Reproduced locally in the pinned node image: `npm install -g +# wrangler@4.120.0` exits 0 with EBADENGINE warnings, then wrangler itself +# exits 1 with "Wrangler requires at least Node.js v22.0.0. You are using +# v20.20.2." Unpinned `npm install -g wrangler` on that same image resolves +# to 4.86.0, because npm picks the newest version the running node +# satisfies -- so 4.86.0 is what has actually been deploying this site, and +# that is what deploy.yml now pins. +# +# Round 4 is the full rehearsal of both jobs end to end with the corrected +# pins, stopping one step short of publishing. name: probe on: @@ -37,24 +54,8 @@ on: - pin-deploy-refs-observable jobs: - # Isolates the wrangler install and invocation from anything to do with - # artifacts. wrangler 4.120.0 declares engines.node >= 22 while the deploy - # container is node 20, so this needs measuring rather than assuming -- - # the pre-issue deploy did run an unpinned wrangler on node:20 - # successfully. - r1-wrangler-only: - runs-on: ubuntu-latest - container: - # node 20.20.2-bookworm, 2026-08-09 - image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 - steps: - # wrangler 4.120.0, 2026-08-09 - - run: npm install -g wrangler@4.120.0 - - run: wrangler --version - - # Producer for the pair that `@v3`/`@v3` resolved to before this issue, - # i.e. the code that was actually deploying the site, now pinned. - r2a-upload-proven: + # Producer: identical to the build job in deploy.yml. + s1-build: runs-on: ubuntu-latest container: # alpine 3.21, 2026-02-28 @@ -74,15 +75,17 @@ jobs: # actions/upload-artifact v3.2.1, 2026-08-09 - uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 with: - name: site-proven + name: site-dry path: site.tar.gz - # Consumer half: the deploy job's artifact handling, with no wrangler, so - # a failure here means the artifact round trip and a pass here means it is - # sound. - r2b-download-proven: + # Consumer: identical to the deploy job in deploy.yml, except that the + # final step prints wrangler's view of the project instead of running + # `wrangler pages deploy`. Same pinned image, same pinned action, same + # pinned wrangler version, same extracted tree. Needs no token and + # publishes nothing. + s2-deploy-dryrun: runs-on: ubuntu-latest - needs: r2a-upload-proven + needs: s1-build container: # node 20.20.2-bookworm, 2026-08-09 image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 @@ -90,40 +93,10 @@ jobs: # actions/download-artifact v3.0.2, 2026-08-09 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: - name: site-proven + name: site-dry - run: tar -xzf site.tar.gz - run: test -f public/index.html - - # The newer node20 artifact pair, kept in the round so the choice between - # the two pairs rests on measurement rather than preference. - r3a-upload-node20: - runs-on: ubuntu-latest - container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - defaults: - run: - shell: sh - steps: - - run: apk add --no-cache nodejs git tar - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - run: tar -czf alt.tar.gz hugo.toml - # actions/upload-artifact v3.2.2-node20, 2026-08-09 - - uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de - with: - name: alt-node20 - path: alt.tar.gz - - r3b-download-node20: - runs-on: ubuntu-latest - needs: r3a-upload-node20 - container: - # node 20.20.2-bookworm, 2026-08-09 - image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 - steps: - # actions/download-artifact v3.1.0-node20, 2026-08-09 - - uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b - with: - name: alt-node20 - - run: test -f alt.tar.gz + # wrangler 4.86.0, 2026-08-09 + - run: npm install -g wrangler@4.86.0 + - run: wrangler --version + - run: wrangler pages deploy --help From 54ed6376af8c5f27508f434ebd7aa7f053b7b2c6 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 9 Aug 2026 03:06:21 +0000 Subject: [PATCH 5/5] Hash-pin every external reference in deploy.yml (closes #7) deploy.yml was the last file in the repo carrying mutable external references. Both job container images are now pinned by digest, all three `uses:` by a full 40-hex commit SHA, and the wrangler install by exact version, each with a version/date comment above the reference. - build container: klakegg/hugo:ext-alpine (abandoned since 2021, mutable tag) replaced by the exact alpine 3.21 digest the Dockerfile already pins, with a pre-checkout `apk add --no-cache nodejs git tar` step, `shell: sh` as the job default, then script/bootstrap and script/test. One pinned base and one dependency list now serve both the check build and the deploy build. - deploy container: node:20 -> node@sha256:8f693eaa... (node 20.20.2 bookworm). - actions/checkout: v4 -> 11bd7190... (v4.2.2), the same SHA check.yml pins. - actions/upload-artifact: -> ff15f030... (v3.2.1). - actions/download-artifact: -> 9bc31d5c... (v3.0.2). - wrangler: `npm install -g wrangler` -> `wrangler@4.86.0`. Also drops the dead feat/initial-site push trigger, reindents to 4-space YAML to match check.yml, and adds `if: github.ref_name == 'main'` to the deploy job so it can never publish from a branch. This is the second attempt. The first passed two adversarial reviews, merged, and broke the deploy, because deploy.yml triggers only on push to main and so nobody could execute what they were reviewing. This time the workflow was temporarily triggered on the branch, with the deploy job guarded off, and iterated against the commit-status API until the build job ran green for real. Doing that found two independent breaks that review had not: 1. actions/upload-artifact v4 fails on this Gitea Actions instance -- artifacts v4 is a different wire protocol and it is not served here. Two otherwise identical branch jobs, one with the v4 upload step and one without, failed and passed respectively. The issue asked for the v3 -> v4 bump; the artifact actions instead stay on the v3 line, pinned by SHA, at the exact commits the mutable @v3 references were already resolving to. Tracked separately in issue 20. 2. wrangler 4.120.0 requires node >= 22 and refuses to start on the pinned node 20 container. `npm install` only warns about engines, so the install step would have passed and the deploy step would have failed. The unpinned command this replaces was never installing `latest` either: npm resolves a bare name to the newest version whose engines the running node satisfies, which on node 20 is 4.86.0. So 4.86.0 is what has actually been deploying this site, and that is what is pinned. Tracked separately in issue 21. The temporary branch trigger and the temporary probe workflow used to bisect this are removed in this commit; the deploy guard is deliberately kept. Verified: make check and script/cibuild green; the build job observed green on the branch under act_runner (commit 73f912c, "Successful in 7s"); a probe job pair rehearsed the deploy job end to end -- same pinned node image, same pinned download action, same pinned wrangler, real site tarball extracted -- stopping at `wrangler pages deploy --help` instead of publishing. The real deploy job remains unexercised: it needs CLOUDFLARE_API_TOKEN and would publish, so it can only run on main. The main run must still be watched and the live site confirmed. --- .gitea/workflows/deploy.yml | 25 ++++----- .gitea/workflows/probe.yml | 102 ------------------------------------ TODO.md | 26 +++++++-- 3 files changed, 32 insertions(+), 121 deletions(-) delete mode 100644 .gitea/workflows/probe.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 515aa1d..8263c4a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -4,10 +4,6 @@ on: push: branches: - main - # TEMPORARY: development-only trigger so the build job actually - # executes under act_runner before this reaches main. Removed in - # the final commit. - - pin-deploy-refs-observable jobs: build: @@ -54,14 +50,14 @@ jobs: - name: Archive site run: tar -czf site.tar.gz public - # v4 does not work on this Gitea Actions instance -- it is what - # broke the deploy in run 25. Measured on this branch: a job - # identical to this one but ending in upload-artifact v4 fails, - # while the same job without that step passes. So this stays on - # the v3 line, pinned, using the node20 build of it rather than - # here; tracked separately. This is the exact commit the mutable - # `@v3` used to resolve to, i.e. the code that was deploying this - # site before this issue -- now pinned instead of floating. + # v3, not v4: artifacts v4 is a different wire protocol and this + # Gitea Actions instance does not serve it. That is what broke the + # deploy in run 25 -- measured by running two otherwise identical + # jobs on a branch, one ending in upload-artifact v4 (failed) and + # one without that step (passed). Tracked in #20. This SHA is the + # exact commit the mutable `@v3` used to resolve to, i.e. the code + # that was already deploying this site, now pinned rather than + # floating. - name: Upload artifact # actions/upload-artifact v3.2.1, 2026-08-09 uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 @@ -102,9 +98,8 @@ jobs: # npm picks the newest version whose engines the running node # satisfies, which on node 20 is exactly 4.86.0. So this pins the # version that has actually been deploying this site, rather than - # silently changing it. Bumping the container to node 22 is the - # alternative; it is a bigger change and is not what this issue is - # for. + # silently changing it. Moving the container to node 22 so the + # wrangler pin can advance is tracked in #21. - name: Install Wrangler # wrangler 4.86.0, 2026-08-09 run: npm install -g wrangler@4.86.0 diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml deleted file mode 100644 index 1223ab0..0000000 --- a/.gitea/workflows/probe.yml +++ /dev/null @@ -1,102 +0,0 @@ -# TEMPORARY diagnostic workflow. Deleted before this branch is merged. -# -# The Actions jobs/logs API is not readable by this account, so the only -# available signal is the commit-status API, which reports one entry per -# *job*. This file therefore encodes the diagnosis as job topology: each job -# isolates one hypothesis and surfaces as its own status context. -# -# Round 1 (2d328e7): -# -# p1 bare alpine + checkout failure 3s -# p2 alpine + apk nodejs git tar + checkout success 5s -# p3 p2 + script/bootstrap + script/test + tar success 15s -# p4 p2 + upload-artifact v4 failure 11s -# p5 node:20-alpine + checkout success 8s -# p6 node:20-bookworm-slim + checkout success 11s -# -# -> the pinned alpine image, the prerequisite step and the site build are all -# fine; upload-artifact v4 is what broke the build job on main. -# -# Round 2 (602fd60): -# -# build (deploy.yml, upload v3.2.2-node20) success 20s -# q1 upload-artifact v3.2.1 (node16) success 7s -# q2 upload-artifact v3.2.1-n20 (node20) success 22s -# q3 full build + upload v3.2.2-node20 success 11s -# q4 download v3.1.0-node20 + wrangler install failure 43s -# -# -> build green on every v3 upload; a second, separate failure on the deploy -# side. -# -# Round 3 (07af755): -# -# build (deploy.yml, upload v3.2.1) success 8s -# r1 wrangler install + invoke, no artifacts failure 7s -# r2a/r2b artifact round trip, v3.2.1/v3.0.2 success 12s / 2s -# r3a/r3b artifact round trip, node20 builds success 8s / 2s -# -# -> the artifact round trip is sound in both pairs; wrangler is the second -# break. Reproduced locally in the pinned node image: `npm install -g -# wrangler@4.120.0` exits 0 with EBADENGINE warnings, then wrangler itself -# exits 1 with "Wrangler requires at least Node.js v22.0.0. You are using -# v20.20.2." Unpinned `npm install -g wrangler` on that same image resolves -# to 4.86.0, because npm picks the newest version the running node -# satisfies -- so 4.86.0 is what has actually been deploying this site, and -# that is what deploy.yml now pins. -# -# Round 4 is the full rehearsal of both jobs end to end with the corrected -# pins, stopping one step short of publishing. -name: probe - -on: - push: - branches: - - pin-deploy-refs-observable - -jobs: - # Producer: identical to the build job in deploy.yml. - s1-build: - runs-on: ubuntu-latest - container: - # alpine 3.21, 2026-02-28 - image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 - defaults: - run: - shell: sh - steps: - - run: apk add --no-cache nodejs git tar - # actions/checkout v4.2.2, 2026-02-28 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - submodules: recursive - - run: script/bootstrap - - run: script/test - - run: tar -czf site.tar.gz public - # actions/upload-artifact v3.2.1, 2026-08-09 - - uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 - with: - name: site-dry - path: site.tar.gz - - # Consumer: identical to the deploy job in deploy.yml, except that the - # final step prints wrangler's view of the project instead of running - # `wrangler pages deploy`. Same pinned image, same pinned action, same - # pinned wrangler version, same extracted tree. Needs no token and - # publishes nothing. - s2-deploy-dryrun: - runs-on: ubuntu-latest - needs: s1-build - container: - # node 20.20.2-bookworm, 2026-08-09 - image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5 - steps: - # actions/download-artifact v3.0.2, 2026-08-09 - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a - with: - name: site-dry - - run: tar -xzf site.tar.gz - - run: test -f public/index.html - # wrangler 4.86.0, 2026-08-09 - - run: npm install -g wrangler@4.86.0 - - run: wrangler --version - - run: wrangler pages deploy --help diff --git a/TODO.md b/TODO.md index c8a581c..7936071 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,8 @@ pre-1.0 No git tags. The site is live and now has the scripts-to-rule-them-all scaffold (`Makefile`, `script/`, `Dockerfile`, `check.yml`); still missing `LICENSE` and -policy files. +policy files. Every external reference in the repo is now pinned by +cryptographic hash (or, for the wrangler CLI install, an exact version). # Next Step @@ -24,6 +25,21 @@ Update `README.md` accordingly. # Completed Steps +- 2026-08-09: hash-pinned every external reference in + `.gitea/workflows/deploy.yml` (closes #7): both job container images are + pinned by digest, all three `uses:` are pinned by 40-hex commit SHA, and the + wrangler install is pinned to an exact version. The abandoned + `klakegg/hugo:ext-alpine` image is gone: the build job now runs on the same + pinned `alpine` digest the `Dockerfile` uses, with a pre-checkout + `apk add nodejs git tar` step (the Actions runner needs `node` inside the job + container to execute JavaScript actions), an explicit `shell: sh` default, + then `script/bootstrap` and `script/test`. The `deploy` job is guarded with + `if: github.ref_name == 'main'` so it can never publish from a branch. Also + dropped the dead `feat/initial-site` push trigger and reindented the file to + 4-space YAML to match `check.yml`. This is the second attempt; the first broke + the deploy and was reverted, so this one was verified by temporarily + triggering the workflow on the PR branch and iterating until the `build` job + ran green for real - 2026-07-25: added the scripts-to-rule-them-all scaffold (closes #4): `script/` entrypoints, `Makefile` shims, a Hugo `Dockerfile` (sha256-pinned alpine) plus `.dockerignore` that runs `make check`, `.gitea/workflows/check.yml` running @@ -40,9 +56,11 @@ Update `README.md` accordingly. # Future Steps -- Pin the images and actions in `deploy.yml` by sha256 - (`klakegg/hugo:ext-alpine`, `node:20`, `actions/checkout`, - `upload`/`download-artifact` are all unpinned) +- Move the artifact actions to v4 once this Gitea Actions instance serves the v4 + artifact protocol; they are pinned on the deprecated v3 line because v4 fails + here (#20) +- Move the deploy container to a pinned node 22 so the wrangler pin can advance + past 4.86.0 (#21) - Rework README.md into the standard sections: Description, Getting Started, Rationale, Design, TODO, License, Author (currently About, Contributing, Technical Details, License)