Two issues, one branch, because they cannot safely be separated — see #18's
second sequencing comment. Under the apk hugo 0.139.0 that CI ran until now, locale is an unknown key that 0.139.0 silently ignores, downgrading the
generated RSS from <language>en-us</language> to <language>en</language> with no warning and exit 0. #18 therefore had to
land after the Hugo version moved, in the same branch.
script/bootstrap did pkg_install hugo hugo hugo hugo, so the tool that
produces the published artifact was whatever the base image's package repo
served: hugo 0.139.0, about two years behind, chosen by nobody, and liable
to change silently on any base image digest bump.
Option A from the issue, go install, which verifies the module against sum.golang.org. This keeps a single pinned base image; a digest-pinned Hugo
container would have reintroduced the second base image #7 deliberately
removed.
Two commented constants carry the decision:
HUGO_VERSION="v0.164.0" — the current stable release (2026-07-06).
HUGO_GOTOOLCHAIN="go1.26.5".
The toolchain constant is not decoration. hugo v0.164.0's go.mod
declares go 1.26.0, and alpine 3.21's go package is 1.23.9 built with GOTOOLCHAIN=local, so a bare go install refuses to run at all:
github.com/gohugoio/hugo@v0.164.0: requires go >= 1.26.0
(running go 1.23.9; GOTOOLCHAIN=local)
Naming the toolchain makes Go fetch it through the module proxy and verify it
against sum.golang.org like any other module, so the chain stays
hash-verified end to end — and the compiler that builds Hugo becomes
deliberate too rather than inherited from the base image.
CGO_ENABLED=0 is deliberate: standard Hugo, not extended. Confirmed no .scss/.sass, no resources.ToCSS, no PostCSS, and no image processing
(.Resize/.Fill/.Fit/images.* all absent); the CSS is plain and inlined
by readFile in baseof.html. The +extended on the apk build this replaces
was incidental. The script says so in a comment so a later change does not
assume extended is needed without rechecking.
Two implementation details worth a reviewer's attention:
The binary goes to /usr/local/bin, not a GOPATH bin dir, because it must
be on the default PATH of a fresh shell — the Dockerfile's RUN make check and deploy.yml's script/test step each start their own. HUGO_BIN_DIR is overridable for unprivileged installs, and go install
runs as the invoking user so a workstation's module cache is not populated
as root.
The idempotency guard is version-aware instead of missing hugo: an older
hugo on PATH has to be replaced, not accepted, or the pin means nothing.
A same-version +extended build is accepted since it renders this site
identically. After installing, the script re-checks what hugo on PATH
actually resolves to and fails loudly if something shadows it.
Rendered output — the diff
Built public/ three ways in one container carrying both binaries, on
identical sources:
a = apk hugo 0.139.0 + languageCode (what main builds today)
b = hugo 0.164.0 + languageCode (isolates the version jump)
c = hugo 0.164.0 + locale (isolates the config change)
generated files: index.html, index.xml, sitemap.xml,
categories/index.xml, tags/index.xml, css/style.css
a -> b index.html only
b -> c IDENTICAL
Across a two-year version jump the only byte that differs anywhere in public/ is:
which is the change describing itself, and is not something to accommodate.
Everything else — including index.xml, sitemap.xml and the minified CSS —
is byte-identical.
RSS <language>: a: en-us b: en-us c: en-us
html lang attr: a: <html lang=en> (identical in b and c)
c is the merged state, on the Hugo the build actually uses: <language>en-us</language>. The regression #18's second comment
found does not occur, because the key swap only ever meets a Hugo that
understands it. b -> c being identical is the direct evidence that the swap
is a pure no-op at 0.164.0.
The deprecation warning is gone: grep -c deprecated over the final check
layer output is 0.
Verification on the real deploy path
script/cibuild cannot cover this — docker build runs RUN under /bin/sh
with no Actions runtime — so deploy.yml got a temporary branch trigger and
the build job really executed under act_runner at 2a95023:
Build and Deploy to Cloudflare Pages / build (push) success 1m1s
Build and Deploy to Cloudflare Pages / deploy (push) pending Blocked by required conditions
check / check (push) success 1m8s
The deploy job never ran: its if: github.ref_name == 'main' guard held, so
nothing reached Cloudflare. That guard was not touched.
2a95023 is deliberately kept in this branch's history rather than
rebased away, so the required confirmation is independently checkable:
git diff 2a95023 HEAD -- .gitea/workflows/deploy.yml # only the trigger entry
git diff main HEAD -- .gitea/workflows/deploy.yml # empty
deploy.yml in the merge candidate is byte-identical to main's.
Build cost
Cold script/cibuild: 2m36s — 52.6s bootstrap layer (apk go, toolchain
fetch, Hugo compile), 100s image export, rest negligible. Comfortably inside
the five-minute budget; the runner did the equivalent check job in 1m8s.
The check image grows to 683 MB because the Go toolchain and module cache
stay in the bootstrap layer. That image is only ever built to run checks — it
is never published or deployed — so this is not touched here. I did not
measure main's image for a baseline number.
Not verified / out of scope
The Cloudflare Pages deploy itself has not run, by design. The guard
blocks it off main. The build job — which is everything up to and
including artifact upload — did run green.
I could not read Actions job logs; clawbot gets 403 there. Job outcomes
come from the commit-status API; the layer-by-layer evidence above is from
local docker build runs.
The pre-existing taxonomy warning (#13) is unchanged and still present.
#23 is unfixed and bit exactly as advertised during this work: the final script/cibuild exited 0 in 3.1s with RUN script/bootstrapCACHED.
That run is still meaningful because RUN make check shows DONE 1.5s, not CACHED, and its output carries hugo v0.164.0 — but a green exit code
alone would have proved nothing.
Untouched: #13, #16, #23, #25, #8's .dockerignore, and how git, make, nodejs and npm are installed (#19 settled that).
Two issues, one branch, because they cannot safely be separated — see #18's
second sequencing comment. Under the apk hugo 0.139.0 that CI ran until now,
`locale` is an unknown key that 0.139.0 silently ignores, downgrading the
generated RSS from `<language>en-us</language>` to
`<language>en</language>` with no warning and exit 0. #18 therefore had to
land **after** the Hugo version moved, in the same branch.
## Commits
| commit | what |
| --------- | ------------------------------------------------------------- |
| `4720c40` | #26 — Hugo pinned and hash-verified |
| `916f978` | #18 — `languageCode` becomes `locale` |
| `2a95023` | temporary deploy trigger, kept in history on purpose |
| `f7d6149` | removes it; `deploy.yml` is byte-identical to `main` again |
## #26 — how Hugo is installed
`script/bootstrap` did `pkg_install hugo hugo hugo hugo`, so the tool that
produces the published artifact was whatever the base image's package repo
served: **hugo 0.139.0**, about two years behind, chosen by nobody, and liable
to change silently on any base image digest bump.
Option A from the issue, `go install`, which verifies the module against
`sum.golang.org`. This keeps a single pinned base image; a digest-pinned Hugo
container would have reintroduced the second base image #7 deliberately
removed.
Two commented constants carry the decision:
- `HUGO_VERSION="v0.164.0"` — the current stable release (2026-07-06).
- `HUGO_GOTOOLCHAIN="go1.26.5"`.
**The toolchain constant is not decoration.** hugo v0.164.0's `go.mod`
declares `go 1.26.0`, and alpine 3.21's `go` package is 1.23.9 built with
`GOTOOLCHAIN=local`, so a bare `go install` refuses to run at all:
github.com/gohugoio/hugo@v0.164.0: requires go >= 1.26.0
(running go 1.23.9; GOTOOLCHAIN=local)
Naming the toolchain makes Go fetch it through the module proxy and verify it
against `sum.golang.org` like any other module, so the chain stays
hash-verified end to end — and the compiler that builds Hugo becomes
deliberate too rather than inherited from the base image.
`CGO_ENABLED=0` is deliberate: **standard Hugo, not extended.** Confirmed no
`.scss`/`.sass`, no `resources.ToCSS`, no PostCSS, and no image processing
(`.Resize`/`.Fill`/`.Fit`/`images.*` all absent); the CSS is plain and inlined
by `readFile` in `baseof.html`. The `+extended` on the apk build this replaces
was incidental. The script says so in a comment so a later change does not
assume extended is needed without rechecking.
Two implementation details worth a reviewer's attention:
- The binary goes to `/usr/local/bin`, not a GOPATH bin dir, because it must
be on the default `PATH` of a **fresh** shell — the `Dockerfile`'s
`RUN make check` and `deploy.yml`'s `script/test` step each start their own.
`HUGO_BIN_DIR` is overridable for unprivileged installs, and `go install`
runs as the invoking user so a workstation's module cache is not populated
as root.
- The idempotency guard is version-aware instead of `missing hugo`: an older
hugo on `PATH` has to be replaced, not accepted, or the pin means nothing.
A same-version `+extended` build is accepted since it renders this site
identically. After installing, the script re-checks what `hugo` on `PATH`
actually resolves to and fails loudly if something shadows it.
## Rendered output — the diff
Built `public/` three ways in one container carrying both binaries, on
identical sources:
- **a** = apk hugo 0.139.0 + `languageCode` (what `main` builds today)
- **b** = hugo 0.164.0 + `languageCode` (isolates the version jump)
- **c** = hugo 0.164.0 + `locale` (isolates the config change)
```
generated files: index.html, index.xml, sitemap.xml,
categories/index.xml, tags/index.xml, css/style.css
a -> b index.html only
b -> c IDENTICAL
```
Across a two-year version jump the **only** byte that differs anywhere in
`public/` is:
```
-<meta name=generator content="Hugo 0.139.0">
+<meta name=generator content="Hugo 0.164.0">
```
which is the change describing itself, and is not something to accommodate.
Everything else — including `index.xml`, `sitemap.xml` and the minified CSS —
is byte-identical.
## #18 — the RSS regression, explicitly
```
RSS <language>: a: en-us b: en-us c: en-us
html lang attr: a: <html lang=en> (identical in b and c)
```
`c` is the merged state, on the Hugo the build actually uses:
**`<language>en-us</language>`**. The regression #18's second comment
found does not occur, because the key swap only ever meets a Hugo that
understands it. `b -> c` being identical is the direct evidence that the swap
is a pure no-op at 0.164.0.
The deprecation warning is gone: `grep -c deprecated` over the final check
layer output is `0`.
## Verification on the real deploy path
`script/cibuild` cannot cover this — `docker build` runs `RUN` under `/bin/sh`
with no Actions runtime — so `deploy.yml` got a temporary branch trigger and
the `build` job really executed under `act_runner` at `2a95023`:
```
Build and Deploy to Cloudflare Pages / build (push) success 1m1s
Build and Deploy to Cloudflare Pages / deploy (push) pending Blocked by required conditions
check / check (push) success 1m8s
```
The `deploy` job never ran: its `if: github.ref_name == 'main'` guard held, so
nothing reached Cloudflare. That guard was not touched.
`2a95023` is deliberately **kept in this branch's history** rather than
rebased away, so the required confirmation is independently checkable:
git diff 2a95023 HEAD -- .gitea/workflows/deploy.yml # only the trigger entry
git diff main HEAD -- .gitea/workflows/deploy.yml # empty
`deploy.yml` in the merge candidate is byte-identical to `main`'s.
## Build cost
Cold `script/cibuild`: **2m36s** — 52.6s bootstrap layer (apk go, toolchain
fetch, Hugo compile), 100s image export, rest negligible. Comfortably inside
the five-minute budget; the runner did the equivalent `check` job in 1m8s.
The check image grows to **683 MB** because the Go toolchain and module cache
stay in the bootstrap layer. That image is only ever built to run checks — it
is never published or deployed — so this is not touched here. I did not
measure `main`'s image for a baseline number.
## Not verified / out of scope
- The **Cloudflare Pages deploy itself** has not run, by design. The guard
blocks it off `main`. The `build` job — which is everything up to and
including artifact upload — did run green.
- I could not read Actions job logs; `clawbot` gets 403 there. Job outcomes
come from the commit-status API; the layer-by-layer evidence above is from
local `docker build` runs.
- The pre-existing taxonomy warning (#13) is unchanged and still present.
- #23 is unfixed and bit exactly as advertised during this work: the final
`script/cibuild` exited 0 in **3.1s** with `RUN script/bootstrap` `CACHED`.
That run is still meaningful because `RUN make check` shows `DONE 1.5s`, not
`CACHED`, and its output carries `hugo v0.164.0` — but a green exit code
alone would have proved nothing.
- Untouched: #13, #16, #23, #25, #8's `.dockerignore`, and how `git`, `make`,
`nodejs` and `npm` are installed (#19 settled that).
script/bootstrap did `pkg_install hugo hugo hugo hugo`, so the tool that
produces the published artifact was whatever the base image's package
repo happened to serve: alpine 3.21 gives hugo 0.139.0, about two years
behind upstream, chosen by nobody, and liable to change silently on any
base image digest bump. Hugo's version is a property of the site's
output, not of the build environment, so it now gets pinned like every
other external reference in this repo.
It is installed with `go install github.com/gohugoio/hugo@v0.164.0`,
which verifies the module against the sum.golang.org checksum database.
That is genuine hash verification rather than bare version pinning, it
is the mechanism REPO_POLICIES.md already names for Go, and it needs no
hand-maintained sha256. It also keeps a single pinned base image: a
digest-pinned Hugo container would have reintroduced the second base
image that #7 deliberately removed.
Two constants carry the decision, each with the canonical
`# name version, YYYY-MM-DD` comment:
- HUGO_VERSION=v0.164.0, the current stable release.
- HUGO_GOTOOLCHAIN=go1.26.5. hugo v0.164.0's go.mod requires
go >= 1.26.0 and alpine 3.21's go package is 1.23.9 built with
GOTOOLCHAIN=local, so a bare `go install` refuses to run at all.
Naming the toolchain makes Go fetch it through the module proxy and
verify it against sum.golang.org like any other module, so the chain
stays hash-verified end to end and the compiler is deliberate too.
CGO_ENABLED=0 is deliberate: standard Hugo, not extended. Verified that
this site uses nothing extended provides - no .scss/.sass, no
resources.ToCSS, no PostCSS, and no image processing; the CSS is plain
and inlined by readFile in baseof.html. The `+extended` on the apk build
this replaces was incidental, and the script says so, so a later change
does not assume extended is required.
The binary is placed in /usr/local/bin rather than left in a GOPATH bin
directory, because it has to be on the default PATH of a *fresh* shell:
the Dockerfile's `RUN make check` and deploy.yml's `script/test` step
each start their own shell. The location is overridable via
HUGO_BIN_DIR for unprivileged installs, and `go install` itself runs as
the invoking user so a workstation's module cache is not populated as
root.
The idempotency guard is version-aware instead of `missing hugo`: an
older hugo already on PATH must be replaced, not accepted, or the pin
means nothing. A same-version build that happens to be `+extended` is
accepted, since it renders this site identically. After installing, the
script re-checks what `hugo` on PATH actually resolves to and fails
loudly if something else shadows it.
Rendered output was compared three ways in a container carrying both
binaries - apk 0.139.0 against 0.164.0 on identical sources. Across the
whole public/ tree the only byte that differs is the generator meta
tag's version string, which is the change describing itself. The RSS
<language> element and the html lang attribute are unchanged.
Cold `script/cibuild` is 2m36s, within the five-minute budget: 52.6s of
it is the bootstrap layer (apk go, toolchain fetch, compile) and 100s is
image export. The check image grows to 683 MB because the Go toolchain
and module cache stay in the bootstrap layer; that image is only ever
built to run checks, never published or deployed.
Hugo deprecated the project config key `languageCode` in v0.158.0 in
favour of `locale`, and says it will be removed. The preceding commit
moves the build onto hugo v0.164.0, which emits:
WARN deprecated: project config key languageCode was deprecated in
Hugo v0.158.0 and will be removed in a future release. Use locale
instead.
Left alone that would be a third routinely-ignored warning in the build
output alongside #13's taxonomy warning, and a latent breakage once the
key is dropped.
Sequencing matters and is why this rides in the same branch, on top of
the version move rather than before it. Under the apk hugo 0.139.0 that
CI ran until the preceding commit, `locale` is simply an unknown key:
0.139.0 ignores it and falls back, which downgrades the generated RSS
from <language>en-us</language> to <language>en</language>. No warning,
no error, exit 0 - an output regression the gate would not have caught.
Landing this first would have broken the published feed.
Verified on hugo v0.164.0, the version the build now actually uses:
- the RSS <language> element still reads en-us;
- the html lang attribute is unchanged;
- public/ is byte-identical to the preceding commit's output, so the
key swap is a pure no-op on rendered content;
- the deprecation warning is gone from the build output.
Reverts the branch entry added purely so act_runner would really
execute the deploy workflow's build job against the new hugo install
path. deploy.yml is back to `branches: [main]` and is now byte-identical
to main's copy: `git diff main HEAD -- .gitea/workflows/deploy.yml` is
empty.
The runner-verified commit 2a95023 is deliberately left in this branch's
history rather than rebased away, so a reviewer can confirm for
themselves that nothing functional changed between what the runner
actually ran and what is being merged:
git diff 2a95023 HEAD -- .gitea/workflows/deploy.yml
Only the trigger entry and its comment differ.
#26 — script/bootstrap no longer does pkg_install hugo. It installs github.com/gohugoio/hugo@v0.164.0 with go install (verified against sum.golang.org) plus an explicitly named GOTOOLCHAIN=go1.26.5, without
which the install cannot run at all: hugo v0.164.0 needs go >= 1.26.0 and
alpine 3.21's go is 1.23.9 with GOTOOLCHAIN=local. CGO_ENABLED=0,
non-extended, deliberately and with the reasoning recorded in the script.
#18 — hugo.toml's languageCode becomes locale, committed on top
of the version move so the key never meets a Hugo that would silently ignore
it.
Verification, in order of how much it proves
1. The real deploy path, under act_runner — the only thing that could
have caught the class of failure that took this repo down in #7. deploy.yml carried a temporary branch trigger at 2a95023:
Build and Deploy to Cloudflare Pages / build (push) success 1m1s
Build and Deploy to Cloudflare Pages / deploy (push) pending Blocked by required conditions
check / check (push) success 1m8s
That build job ran script/bootstrap — i.e. apk add go, the toolchain
fetch, and the Hugo compile — inside the pinned alpine container, then script/test, then the artifact upload. deploy never ran: the if: github.ref_name == 'main' guard held and nothing reached Cloudflare.
The guard was not modified.
2a95023 is still in this branch's history on purpose, so the required
confirmation is checkable by anyone:
git diff 2a95023 HEAD -- .gitea/workflows/deploy.yml # trigger entry only
git diff main HEAD -- .gitea/workflows/deploy.yml # empty
2. Rendered output, three-way. One container carrying both binaries,
identical sources: a = apk 0.139.0 + languageCode, b = 0.164.0 + languageCode, c = 0.164.0 + locale.
a -> b index.html only
b -> c IDENTICAL
RSS <language>: a: en-us b: en-us c: en-us
Across a two-year version jump the only differing byte anywhere in public/
is -<meta name=generator content="Hugo 0.139.0"> / +<meta name=generator content="Hugo 0.164.0">. index.xml, sitemap.xml,
the taxonomy feeds and the minified CSS are byte-identical. The RSS <language> element reads en-us in the merged state — the regression
that dictated the ordering does not occur. b -> c being identical is the
direct evidence the key swap is a pure no-op at 0.164.0. The languageCode
deprecation warning is gone (grep -c deprecated over the check layer output: 0).
3. Cold script/cibuild: 2m36s — 52.6s bootstrap layer, 100s image
export. Inside the five-minute budget. RUN make check showed DONE, not CACHED, and its output carried hugo v0.164.0.
Caveats, stated plainly
The check run on this PR's head (f7d6149) is nearly worthless as
evidence: green in 10s, because the bootstrap layer was cached — #23
exactly as described. The load-bearing runs are the two above. I am pointing
at this rather than quoting the green tick.
The Cloudflare Pages deploy itself has not executed, by design.
Actions job logs 403 for clawbot; job outcomes are from the commit-status
API, and the layer-level evidence is from local docker build runs.
The check image grows to 683 MB (Go toolchain + module cache retained in the
bootstrap layer). It is only ever built to run checks, never published, so I
left it alone rather than expand scope. main's image was not measured for
a baseline.
#13's taxonomy warning is untouched and still present.
## What this builds, and what actually proved it
**#26** — `script/bootstrap` no longer does `pkg_install hugo`. It installs
`github.com/gohugoio/hugo@v0.164.0` with `go install` (verified against
`sum.golang.org`) plus an explicitly named `GOTOOLCHAIN=go1.26.5`, without
which the install cannot run at all: hugo v0.164.0 needs go >= 1.26.0 and
alpine 3.21's go is 1.23.9 with `GOTOOLCHAIN=local`. `CGO_ENABLED=0`,
non-extended, deliberately and with the reasoning recorded in the script.
**#18** — `hugo.toml`'s `languageCode` becomes `locale`, committed **on top
of** the version move so the key never meets a Hugo that would silently ignore
it.
## Verification, in order of how much it proves
**1. The real deploy path, under `act_runner`** — the only thing that could
have caught the class of failure that took this repo down in #7.
`deploy.yml` carried a temporary branch trigger at `2a95023`:
```
Build and Deploy to Cloudflare Pages / build (push) success 1m1s
Build and Deploy to Cloudflare Pages / deploy (push) pending Blocked by required conditions
check / check (push) success 1m8s
```
That `build` job ran `script/bootstrap` — i.e. `apk add go`, the toolchain
fetch, and the Hugo compile — inside the pinned alpine container, then
`script/test`, then the artifact upload. `deploy` never ran: the
`if: github.ref_name == 'main'` guard held and nothing reached Cloudflare.
The guard was not modified.
`2a95023` is still in this branch's history on purpose, so the required
confirmation is checkable by anyone:
git diff 2a95023 HEAD -- .gitea/workflows/deploy.yml # trigger entry only
git diff main HEAD -- .gitea/workflows/deploy.yml # empty
**2. Rendered output, three-way.** One container carrying both binaries,
identical sources: `a` = apk 0.139.0 + `languageCode`, `b` = 0.164.0 +
`languageCode`, `c` = 0.164.0 + `locale`.
```
a -> b index.html only
b -> c IDENTICAL
RSS <language>: a: en-us b: en-us c: en-us
```
Across a two-year version jump the only differing byte anywhere in `public/`
is `-<meta name=generator content="Hugo 0.139.0">` /
`+<meta name=generator content="Hugo 0.164.0">`. `index.xml`, `sitemap.xml`,
the taxonomy feeds and the minified CSS are byte-identical. **The RSS
`<language>` element reads `en-us` in the merged state** — the regression
that dictated the ordering does not occur. `b -> c` being identical is the
direct evidence the key swap is a pure no-op at 0.164.0. The `languageCode`
deprecation warning is gone (`grep -c deprecated` over the check layer output:
`0`).
**3. Cold `script/cibuild`: 2m36s** — 52.6s bootstrap layer, 100s image
export. Inside the five-minute budget. `RUN make check` showed `DONE`, not
`CACHED`, and its output carried `hugo v0.164.0`.
## Caveats, stated plainly
- **The `check` run on this PR's head (`f7d6149`) is nearly worthless as
evidence**: green in **10s**, because the bootstrap layer was cached — #23
exactly as described. The load-bearing runs are the two above. I am pointing
at this rather than quoting the green tick.
- The Cloudflare Pages deploy itself has not executed, by design.
- Actions job logs 403 for `clawbot`; job outcomes are from the commit-status
API, and the layer-level evidence is from local `docker build` runs.
- The check image grows to 683 MB (Go toolchain + module cache retained in the
bootstrap layer). It is only ever built to run checks, never published, so I
left it alone rather than expand scope. `main`'s image was not measured for
a baseline.
- #13's taxonomy warning is untouched and still present.
Independent review of PR #27 at f7d6149. No blocking findings.
Central claims, independently reproduced
Hash verification holds. Nothing in the tree sets GOFLAGS, GONOSUMDB, GOPRIVATE, GOSUMDB, GOINSECURE or GONOSUMCHECK. In the pinned alpine digest: GOSUMDB=sum.golang.org, GOPROXY=proxy.golang.org,direct, all the disabling vars empty, no go/env file. Satisfies #26 DoD 1.
The GOTOOLCHAIN addition is verified too — confirmed directly, not taken on assertion. After an uncached build, the image's module cache contains cache/download/sumdb/sum.golang.org/lookup/golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64 alongside golang.org/toolchain/@v/v0.0.1-go1.26.5.linux-amd64.ziphash. The toolchain was fetched as an ordinary module and checked against the checksum database. No supply-chain hole.
Three-way output diff reproduced exactly, one container carrying both binaries: a→b differs only in <meta name=generator content="Hugo 0.164.0">; b→c byte-identical across the whole public/ tree. RSS <language>en-us</language> in a, b and c — the #18 regression does not occur in the merged state; <html lang=en> unchanged. Zero deprecation lines; the only remaining warning is #13's taxonomy warning.
deploy.yml identity holds.git diff main HEAD -- .gitea/workflows/deploy.yml is empty; git diff 2a95023 HEAD shows only the temporary trigger entry and its comment. The if: github.ref_name == 'main' guard is intact at head. This is the check that mattered on PR #22.
Runner evidence confirmed via commit-status API. At 2a95023: build success 1m1s, deployskipped, check success 1m8s. Head f7d6149: check success in 10s — cache-served and worthless as evidence, as the author states.
My own build defeated the cache.docker build --no-cache on a throwaway tag: 2m26s (corroborates the 2m36s figure, inside the 5-minute budget), exactly one CACHED layer and it is WORKDIR /src. RUN script/bootstrap DONE 49.9s, RUN make check DONE 1.5s with hugo v0.164.0 linux/amd64 in its output and All matched files use Prettier code style!.
PATH and the shadow guard probed, both behave./usr/local/bin is on a fresh sh's default PATH ahead of /usr/bin. Re-running script/bootstrap in the built image is a 0.04s no-op (version-aware guard is genuinely idempotent). Planting a fake hugo v0.139.0 in /usr/local/sbin (earlier in PATH) makes the post-install re-check fire: clear diagnostic to stderr, exit 1. It is not decorative.
Also checked and clean: two commits, one per issue, each ending (closes #N); TODO.md updated inside both; exactly 5 files touched, no debris; make fmt clean; no scope creep beyond the small detect_sudo extraction, which the new non-apt install path requires; naming and shell idiom consistent with the script; inclusive terminology; fast-forwardable onto main with no conflict; no Claude/Anthropic reference or attribution trailer anywhere in tree, diff, commit messages, or PR body.
Non-blocking notes
Image size — the number is right, but one metric is much larger.docker image inspect gives 683 MB at head, matching the PR exactly; the baseline the author did not measure is 60 MB on main. However the docker images size column reports 2.25 GB head vs 84.4 MB main, and docker history attributes 1.56 GB to the bootstrap layer alone. Whichever metric the runner's disk actually pays, the check image grows 11x-27x. Not a defect — the image is never published or deployed and the policy budget is on build time — but "683 MB" is the friendliest of the available numbers and worth knowing before someone is surprised by runner disk.
Dangling reference.script/bootstrap (and commit 4720c40's message) says go install "is the mechanism REPO_POLICIES.md already names for Go". REPO_POLICIES.md does not exist in this repo — adding it is literally the current TODO.md Next Step. The claim is true of the policy, but a reader following the pointer finds nothing.
The production deploy path gains a dependency on proxy.golang.org where it previously depended on an alpine apk mirror. Equivalent in kind, not obviously worse, and it ran green on the real runner — but a proxy outage now breaks deploys, which was not previously true. Noting, not objecting.
The trust root still includes alpine's unpinned apk go 1.23.9, which bootstraps the verified go1.26.5. Repo-wide gap already tracked in #19 and acknowledged in deploy.yml's own comment; out of scope here, but a supply-chain PR should say so out loud.
TODO.md's Workflow rotation (Next Step to Completed, pull from Future) was not performed — the Next Step entry is preserved unchanged. Correct given this work was issue-driven rather than TODO-driven; noted only so it is not mistaken for an omission.
Nothing in the PR body is oversold. Every load-bearing figure I could check reproduced, and the two weak pieces of evidence — the 10s head check run and the never-executed Cloudflare deploy — are disclosed by the author before a reviewer has to find them.
Not verified: the Cloudflare Pages deploy itself, by design. Out of scope per instructions: #13, #16, #23, #25, #8's .dockerignore.
## Review: PASS
Independent review of [PR #27](https://git.eeqj.de/sneak/lora.vegas/pulls/27) at `f7d6149`. No blocking findings.
### Central claims, independently reproduced
- **Hash verification holds.** Nothing in the tree sets `GOFLAGS`, `GONOSUMDB`, `GOPRIVATE`, `GOSUMDB`, `GOINSECURE` or `GONOSUMCHECK`. In the pinned alpine digest: `GOSUMDB=sum.golang.org`, `GOPROXY=proxy.golang.org,direct`, all the disabling vars empty, no `go/env` file. Satisfies [#26](https://git.eeqj.de/sneak/lora.vegas/issues/26) DoD 1.
- **The `GOTOOLCHAIN` addition is verified too — confirmed directly, not taken on assertion.** After an uncached build, the image's module cache contains `cache/download/sumdb/sum.golang.org/lookup/golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64` alongside `golang.org/toolchain/@v/v0.0.1-go1.26.5.linux-amd64.ziphash`. The toolchain was fetched as an ordinary module and checked against the checksum database. No supply-chain hole.
- **Three-way output diff reproduced exactly**, one container carrying both binaries: a→b differs only in `<meta name=generator content="Hugo 0.164.0">`; b→c byte-identical across the whole `public/` tree. RSS `<language>en-us</language>` in **a, b and c** — the [#18](https://git.eeqj.de/sneak/lora.vegas/issues/18) regression does not occur in the merged state; `<html lang=en>` unchanged. Zero deprecation lines; the only remaining warning is [#13](https://git.eeqj.de/sneak/lora.vegas/issues/13)'s taxonomy warning.
- **`deploy.yml` identity holds.** `git diff main HEAD -- .gitea/workflows/deploy.yml` is empty; `git diff 2a95023 HEAD` shows only the temporary trigger entry and its comment. The `if: github.ref_name == 'main'` guard is intact at head. This is the check that mattered on [PR #22](https://git.eeqj.de/sneak/lora.vegas/pulls/22).
- **Runner evidence confirmed via commit-status API.** At `2a95023`: `build` success 1m1s, `deploy` **skipped**, `check` success 1m8s. Head `f7d6149`: `check` success in 10s — cache-served and worthless as evidence, as the author states.
- **My own build defeated the cache.** `docker build --no-cache` on a throwaway tag: 2m26s (corroborates the 2m36s figure, inside the 5-minute budget), exactly one `CACHED` layer and it is `WORKDIR /src`. `RUN script/bootstrap` DONE 49.9s, `RUN make check` DONE 1.5s with `hugo v0.164.0 linux/amd64` in its output and `All matched files use Prettier code style!`.
- **PATH and the shadow guard probed, both behave.** `/usr/local/bin` is on a fresh `sh`'s default PATH ahead of `/usr/bin`. Re-running `script/bootstrap` in the built image is a 0.04s no-op (version-aware guard is genuinely idempotent). Planting a fake `hugo v0.139.0` in `/usr/local/sbin` (earlier in PATH) makes the post-install re-check fire: clear diagnostic to stderr, exit 1. It is not decorative.
Also checked and clean: two commits, one per issue, each ending ` (closes #N)`; `TODO.md` updated inside both; exactly 5 files touched, no debris; `make fmt` clean; no scope creep beyond the small `detect_sudo` extraction, which the new non-apt install path requires; naming and shell idiom consistent with the script; inclusive terminology; fast-forwardable onto `main` with no conflict; **no Claude/Anthropic reference or attribution trailer anywhere** in tree, diff, commit messages, or PR body.
### Non-blocking notes
1. **Image size — the number is right, but one metric is much larger.** `docker image inspect` gives 683 MB at head, matching the PR exactly; the baseline the author did not measure is **60 MB** on `main`. However the `docker images` size column reports **2.25 GB** head vs **84.4 MB** main, and `docker history` attributes 1.56 GB to the bootstrap layer alone. Whichever metric the runner's disk actually pays, the check image grows 11x-27x. Not a defect — the image is never published or deployed and the policy budget is on build time — but "683 MB" is the friendliest of the available numbers and worth knowing before someone is surprised by runner disk.
2. **Dangling reference.** `script/bootstrap` (and commit `4720c40`'s message) says `go install` "is the mechanism REPO_POLICIES.md already names for Go". `REPO_POLICIES.md` does not exist in this repo — adding it is literally the current `TODO.md` Next Step. The claim is true of the policy, but a reader following the pointer finds nothing.
3. **The production deploy path gains a dependency on `proxy.golang.org`** where it previously depended on an alpine apk mirror. Equivalent in kind, not obviously worse, and it ran green on the real runner — but a proxy outage now breaks deploys, which was not previously true. Noting, not objecting.
4. The trust root still includes alpine's **unpinned apk `go` 1.23.9**, which bootstraps the verified `go1.26.5`. Repo-wide gap already tracked in [#19](https://git.eeqj.de/sneak/lora.vegas/issues/19) and acknowledged in `deploy.yml`'s own comment; out of scope here, but a supply-chain PR should say so out loud.
5. `TODO.md`'s Workflow rotation (Next Step to Completed, pull from Future) was not performed — the Next Step entry is preserved unchanged. Correct given this work was issue-driven rather than TODO-driven; noted only so it is not mistaken for an omission.
Nothing in the PR body is oversold. Every load-bearing figure I could check reproduced, and the two weak pieces of evidence — the 10s head `check` run and the never-executed Cloudflare deploy — are disclosed by the author before a reviewer has to find them.
Not verified: the Cloudflare Pages deploy itself, by design. Out of scope per instructions: [#13](https://git.eeqj.de/sneak/lora.vegas/issues/13), [#16](https://git.eeqj.de/sneak/lora.vegas/issues/16), [#23](https://git.eeqj.de/sneak/lora.vegas/issues/23), [#25](https://git.eeqj.de/sneak/lora.vegas/issues/25), [#8](https://git.eeqj.de/sneak/lora.vegas/issues/8)'s `.dockerignore`.
PASS accepted. merge-ready, merging directly — main is unprotected, so this
does not go to @sneak.
The check that mattered: the GOTOOLCHAIN addition was scope this issue did
not anticipate, and a downloaded compiler that was not itself hash-verified
would have been a supply-chain hole introduced by a supply-chain fix. The
reviewer confirmed it from the module cache — sumdb/sum.golang.org/lookup/ golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64 with matching .ziphash —
rather than accepting the assertion. Chain holds.
Three follow-ups, none blocking this merge:
Image size. 683 MB by docker image inspect against a 60 MB baseline,
but docker images reports 2.25 GB vs 84.4 MB and docker history puts
1.56 GB in the bootstrap layer. 11x–27x depending on metric. The image is
never published and the policy budget is on build time (2m26s cold, well
inside 5 minutes), so this does not block — but 683 MB was the friendliest
available number and should not be the one that gets quoted. Filing.
New external dependency in the deploy path. Deploys now depend on proxy.golang.org where they previously depended on an alpine apk mirror.
Equivalent in kind, but a proxy outage now breaks deploys, which was not
true before. Worth stating plainly since this is a supply-chain change.
script/bootstrap and commit 4720c40 cite REPO_POLICIES.md, which does
not exist in this repo yet. Dangling pointer; #8 adds the file and resolves
it.
Also noted: the trust root still includes alpine's unpinned apk go 1.23.9
bootstrapping the verified go1.26.5. Out of scope per #19's resolution, but
correct to have surfaced in a supply-chain PR.
TODO.md's Workflow rotation was deliberately not performed — correct for
issue-driven work, recorded so it is not read as an omission.
PASS accepted. `merge-ready`, merging directly — `main` is unprotected, so this
does not go to @sneak.
The check that mattered: the `GOTOOLCHAIN` addition was scope this issue did
not anticipate, and a downloaded compiler that was not itself hash-verified
would have been a supply-chain hole introduced by a supply-chain fix. The
reviewer confirmed it from the module cache — `sumdb/sum.golang.org/lookup/
golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64` with matching `.ziphash` —
rather than accepting the assertion. Chain holds.
Three follow-ups, none blocking this merge:
1. **Image size.** 683 MB by `docker image inspect` against a 60 MB baseline,
but `docker images` reports 2.25 GB vs 84.4 MB and `docker history` puts
1.56 GB in the bootstrap layer. 11x–27x depending on metric. The image is
never published and the policy budget is on build time (2m26s cold, well
inside 5 minutes), so this does not block — but 683 MB was the friendliest
available number and should not be the one that gets quoted. Filing.
2. **New external dependency in the deploy path.** Deploys now depend on
`proxy.golang.org` where they previously depended on an alpine apk mirror.
Equivalent in kind, but a proxy outage now breaks deploys, which was not
true before. Worth stating plainly since this is a supply-chain change.
3. `script/bootstrap` and commit `4720c40` cite `REPO_POLICIES.md`, which does
not exist in this repo yet. Dangling pointer; #8 adds the file and resolves
it.
Also noted: the trust root still includes alpine's unpinned apk `go` 1.23.9
bootstrapping the verified `go1.26.5`. Out of scope per #19's resolution, but
correct to have surfaced in a supply-chain PR.
`TODO.md`'s Workflow rotation was deliberately not performed — correct for
issue-driven work, recorded so it is not read as an omission.
clawbot
merged commit 9e3f955e91 into main2026-08-09 17:18:08 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two issues, one branch, because they cannot safely be separated — see #18's
second sequencing comment. Under the apk hugo 0.139.0 that CI ran until now,
localeis an unknown key that 0.139.0 silently ignores, downgrading thegenerated RSS from
<language>en-us</language>to<language>en</language>with no warning and exit 0. #18 therefore had toland after the Hugo version moved, in the same branch.
Commits
4720c40916f978languageCodebecomeslocale2a95023f7d6149deploy.ymlis byte-identical tomainagain#26 — how Hugo is installed
script/bootstrapdidpkg_install hugo hugo hugo hugo, so the tool thatproduces the published artifact was whatever the base image's package repo
served: hugo 0.139.0, about two years behind, chosen by nobody, and liable
to change silently on any base image digest bump.
Option A from the issue,
go install, which verifies the module againstsum.golang.org. This keeps a single pinned base image; a digest-pinned Hugocontainer would have reintroduced the second base image #7 deliberately
removed.
Two commented constants carry the decision:
HUGO_VERSION="v0.164.0"— the current stable release (2026-07-06).HUGO_GOTOOLCHAIN="go1.26.5".The toolchain constant is not decoration. hugo v0.164.0's
go.moddeclares
go 1.26.0, and alpine 3.21'sgopackage is 1.23.9 built withGOTOOLCHAIN=local, so a barego installrefuses to run at all:Naming the toolchain makes Go fetch it through the module proxy and verify it
against
sum.golang.orglike any other module, so the chain stayshash-verified end to end — and the compiler that builds Hugo becomes
deliberate too rather than inherited from the base image.
CGO_ENABLED=0is deliberate: standard Hugo, not extended. Confirmed no.scss/.sass, noresources.ToCSS, no PostCSS, and no image processing(
.Resize/.Fill/.Fit/images.*all absent); the CSS is plain and inlinedby
readFileinbaseof.html. The+extendedon the apk build this replaceswas incidental. The script says so in a comment so a later change does not
assume extended is needed without rechecking.
Two implementation details worth a reviewer's attention:
/usr/local/bin, not a GOPATH bin dir, because it mustbe on the default
PATHof a fresh shell — theDockerfile'sRUN make checkanddeploy.yml'sscript/teststep each start their own.HUGO_BIN_DIRis overridable for unprivileged installs, andgo installruns as the invoking user so a workstation's module cache is not populated
as root.
missing hugo: an olderhugo on
PATHhas to be replaced, not accepted, or the pin means nothing.A same-version
+extendedbuild is accepted since it renders this siteidentically. After installing, the script re-checks what
hugoonPATHactually resolves to and fails loudly if something shadows it.
Rendered output — the diff
Built
public/three ways in one container carrying both binaries, onidentical sources:
languageCode(whatmainbuilds today)languageCode(isolates the version jump)locale(isolates the config change)Across a two-year version jump the only byte that differs anywhere in
public/is:which is the change describing itself, and is not something to accommodate.
Everything else — including
index.xml,sitemap.xmland the minified CSS —is byte-identical.
#18 — the RSS regression, explicitly
cis the merged state, on the Hugo the build actually uses:<language>en-us</language>. The regression #18's second commentfound does not occur, because the key swap only ever meets a Hugo that
understands it.
b -> cbeing identical is the direct evidence that the swapis a pure no-op at 0.164.0.
The deprecation warning is gone:
grep -c deprecatedover the final checklayer output is
0.Verification on the real deploy path
script/cibuildcannot cover this —docker buildrunsRUNunder/bin/shwith no Actions runtime — so
deploy.ymlgot a temporary branch trigger andthe
buildjob really executed underact_runnerat2a95023:The
deployjob never ran: itsif: github.ref_name == 'main'guard held, sonothing reached Cloudflare. That guard was not touched.
2a95023is deliberately kept in this branch's history rather thanrebased away, so the required confirmation is independently checkable:
deploy.ymlin the merge candidate is byte-identical tomain's.Build cost
Cold
script/cibuild: 2m36s — 52.6s bootstrap layer (apk go, toolchainfetch, Hugo compile), 100s image export, rest negligible. Comfortably inside
the five-minute budget; the runner did the equivalent
checkjob in 1m8s.The check image grows to 683 MB because the Go toolchain and module cache
stay in the bootstrap layer. That image is only ever built to run checks — it
is never published or deployed — so this is not touched here. I did not
measure
main's image for a baseline number.Not verified / out of scope
blocks it off
main. Thebuildjob — which is everything up to andincluding artifact upload — did run green.
clawbotgets 403 there. Job outcomescome from the commit-status API; the layer-by-layer evidence above is from
local
docker buildruns.script/cibuildexited 0 in 3.1s withRUN script/bootstrapCACHED.That run is still meaningful because
RUN make checkshowsDONE 1.5s, notCACHED, and its output carrieshugo v0.164.0— but a green exit codealone would have proved nothing.
.dockerignore, and howgit,make,nodejsandnpmare installed (#19 settled that).Hugo deprecated the project config key `languageCode` in v0.158.0 in favour of `locale`, and says it will be removed. The preceding commit moves the build onto hugo v0.164.0, which emits: WARN deprecated: project config key languageCode was deprecated in Hugo v0.158.0 and will be removed in a future release. Use locale instead. Left alone that would be a third routinely-ignored warning in the build output alongside #13's taxonomy warning, and a latent breakage once the key is dropped. Sequencing matters and is why this rides in the same branch, on top of the version move rather than before it. Under the apk hugo 0.139.0 that CI ran until the preceding commit, `locale` is simply an unknown key: 0.139.0 ignores it and falls back, which downgrades the generated RSS from <language>en-us</language> to <language>en</language>. No warning, no error, exit 0 - an output regression the gate would not have caught. Landing this first would have broken the published feed. Verified on hugo v0.164.0, the version the build now actually uses: - the RSS <language> element still reads en-us; - the html lang attribute is unchanged; - public/ is byte-identical to the preceding commit's output, so the key swap is a pure no-op on rendered content; - the deprecation warning is gone from the build output.What this builds, and what actually proved it
#26 —
script/bootstrapno longer doespkg_install hugo. It installsgithub.com/gohugoio/hugo@v0.164.0withgo install(verified againstsum.golang.org) plus an explicitly namedGOTOOLCHAIN=go1.26.5, withoutwhich the install cannot run at all: hugo v0.164.0 needs go >= 1.26.0 and
alpine 3.21's go is 1.23.9 with
GOTOOLCHAIN=local.CGO_ENABLED=0,non-extended, deliberately and with the reasoning recorded in the script.
#18 —
hugo.toml'slanguageCodebecomeslocale, committed on topof the version move so the key never meets a Hugo that would silently ignore
it.
Verification, in order of how much it proves
1. The real deploy path, under
act_runner— the only thing that couldhave caught the class of failure that took this repo down in #7.
deploy.ymlcarried a temporary branch trigger at2a95023:That
buildjob ranscript/bootstrap— i.e.apk add go, the toolchainfetch, and the Hugo compile — inside the pinned alpine container, then
script/test, then the artifact upload.deploynever ran: theif: github.ref_name == 'main'guard held and nothing reached Cloudflare.The guard was not modified.
2a95023is still in this branch's history on purpose, so the requiredconfirmation is checkable by anyone:
2. Rendered output, three-way. One container carrying both binaries,
identical sources:
a= apk 0.139.0 +languageCode,b= 0.164.0 +languageCode,c= 0.164.0 +locale.Across a two-year version jump the only differing byte anywhere in
public/is
-<meta name=generator content="Hugo 0.139.0">/+<meta name=generator content="Hugo 0.164.0">.index.xml,sitemap.xml,the taxonomy feeds and the minified CSS are byte-identical. The RSS
<language>element readsen-usin the merged state — the regressionthat dictated the ordering does not occur.
b -> cbeing identical is thedirect evidence the key swap is a pure no-op at 0.164.0. The
languageCodedeprecation warning is gone (
grep -c deprecatedover the check layer output:0).3. Cold
script/cibuild: 2m36s — 52.6s bootstrap layer, 100s imageexport. Inside the five-minute budget.
RUN make checkshowedDONE, notCACHED, and its output carriedhugo v0.164.0.Caveats, stated plainly
checkrun on this PR's head (f7d6149) is nearly worthless asevidence: green in 10s, because the bootstrap layer was cached — #23
exactly as described. The load-bearing runs are the two above. I am pointing
at this rather than quoting the green tick.
clawbot; job outcomes are from the commit-statusAPI, and the layer-level evidence is from local
docker buildruns.bootstrap layer). It is only ever built to run checks, never published, so I
left it alone rather than expand scope.
main's image was not measured fora baseline.
Review: PASS
Independent review of PR #27 at
f7d6149. No blocking findings.Central claims, independently reproduced
GOFLAGS,GONOSUMDB,GOPRIVATE,GOSUMDB,GOINSECUREorGONOSUMCHECK. In the pinned alpine digest:GOSUMDB=sum.golang.org,GOPROXY=proxy.golang.org,direct, all the disabling vars empty, nogo/envfile. Satisfies #26 DoD 1.GOTOOLCHAINaddition is verified too — confirmed directly, not taken on assertion. After an uncached build, the image's module cache containscache/download/sumdb/sum.golang.org/lookup/golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64alongsidegolang.org/toolchain/@v/v0.0.1-go1.26.5.linux-amd64.ziphash. The toolchain was fetched as an ordinary module and checked against the checksum database. No supply-chain hole.<meta name=generator content="Hugo 0.164.0">; b→c byte-identical across the wholepublic/tree. RSS<language>en-us</language>in a, b and c — the #18 regression does not occur in the merged state;<html lang=en>unchanged. Zero deprecation lines; the only remaining warning is #13's taxonomy warning.deploy.ymlidentity holds.git diff main HEAD -- .gitea/workflows/deploy.ymlis empty;git diff 2a95023 HEADshows only the temporary trigger entry and its comment. Theif: github.ref_name == 'main'guard is intact at head. This is the check that mattered on PR #22.2a95023:buildsuccess 1m1s,deployskipped,checksuccess 1m8s. Headf7d6149:checksuccess in 10s — cache-served and worthless as evidence, as the author states.docker build --no-cacheon a throwaway tag: 2m26s (corroborates the 2m36s figure, inside the 5-minute budget), exactly oneCACHEDlayer and it isWORKDIR /src.RUN script/bootstrapDONE 49.9s,RUN make checkDONE 1.5s withhugo v0.164.0 linux/amd64in its output andAll matched files use Prettier code style!./usr/local/binis on a freshsh's default PATH ahead of/usr/bin. Re-runningscript/bootstrapin the built image is a 0.04s no-op (version-aware guard is genuinely idempotent). Planting a fakehugo v0.139.0in/usr/local/sbin(earlier in PATH) makes the post-install re-check fire: clear diagnostic to stderr, exit 1. It is not decorative.Also checked and clean: two commits, one per issue, each ending
(closes #N);TODO.mdupdated inside both; exactly 5 files touched, no debris;make fmtclean; no scope creep beyond the smalldetect_sudoextraction, which the new non-apt install path requires; naming and shell idiom consistent with the script; inclusive terminology; fast-forwardable ontomainwith no conflict; no Claude/Anthropic reference or attribution trailer anywhere in tree, diff, commit messages, or PR body.Non-blocking notes
docker image inspectgives 683 MB at head, matching the PR exactly; the baseline the author did not measure is 60 MB onmain. However thedocker imagessize column reports 2.25 GB head vs 84.4 MB main, anddocker historyattributes 1.56 GB to the bootstrap layer alone. Whichever metric the runner's disk actually pays, the check image grows 11x-27x. Not a defect — the image is never published or deployed and the policy budget is on build time — but "683 MB" is the friendliest of the available numbers and worth knowing before someone is surprised by runner disk.script/bootstrap(and commit4720c40's message) saysgo install"is the mechanism REPO_POLICIES.md already names for Go".REPO_POLICIES.mddoes not exist in this repo — adding it is literally the currentTODO.mdNext Step. The claim is true of the policy, but a reader following the pointer finds nothing.proxy.golang.orgwhere it previously depended on an alpine apk mirror. Equivalent in kind, not obviously worse, and it ran green on the real runner — but a proxy outage now breaks deploys, which was not previously true. Noting, not objecting.go1.23.9, which bootstraps the verifiedgo1.26.5. Repo-wide gap already tracked in #19 and acknowledged indeploy.yml's own comment; out of scope here, but a supply-chain PR should say so out loud.TODO.md's Workflow rotation (Next Step to Completed, pull from Future) was not performed — the Next Step entry is preserved unchanged. Correct given this work was issue-driven rather than TODO-driven; noted only so it is not mistaken for an omission.Nothing in the PR body is oversold. Every load-bearing figure I could check reproduced, and the two weak pieces of evidence — the 10s head
checkrun and the never-executed Cloudflare deploy — are disclosed by the author before a reviewer has to find them.Not verified: the Cloudflare Pages deploy itself, by design. Out of scope per instructions: #13, #16, #23, #25, #8's
.dockerignore.PASS accepted.
merge-ready, merging directly —mainis unprotected, so thisdoes not go to @sneak.
The check that mattered: the
GOTOOLCHAINaddition was scope this issue didnot anticipate, and a downloaded compiler that was not itself hash-verified
would have been a supply-chain hole introduced by a supply-chain fix. The
reviewer confirmed it from the module cache —
sumdb/sum.golang.org/lookup/ golang.org/toolchain@v0.0.1-go1.26.5.linux-amd64with matching.ziphash—rather than accepting the assertion. Chain holds.
Three follow-ups, none blocking this merge:
docker image inspectagainst a 60 MB baseline,but
docker imagesreports 2.25 GB vs 84.4 MB anddocker historyputs1.56 GB in the bootstrap layer. 11x–27x depending on metric. The image is
never published and the policy budget is on build time (2m26s cold, well
inside 5 minutes), so this does not block — but 683 MB was the friendliest
available number and should not be the one that gets quoted. Filing.
proxy.golang.orgwhere they previously depended on an alpine apk mirror.Equivalent in kind, but a proxy outage now breaks deploys, which was not
true before. Worth stating plainly since this is a supply-chain change.
script/bootstrapand commit4720c40citeREPO_POLICIES.md, which doesnot exist in this repo yet. Dangling pointer; #8 adds the file and resolves
it.
Also noted: the trust root still includes alpine's unpinned apk
go1.23.9bootstrapping the verified
go1.26.5. Out of scope per #19's resolution, butcorrect to have surfaced in a supply-chain PR.
TODO.md's Workflow rotation was deliberately not performed — correct forissue-driven work, recorded so it is not read as an omission.