Pin Hugo to a deliberate, hash-verified v0.164.0 and switch hugo.toml to locale (closes #26, closes #18) #27

Merged
clawbot merged 4 commits from 26-hugo-deliberate-version into main 2026-08-09 17:18:08 +02:00
Collaborator

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 #18languageCode 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:
&lt;language&gt;en-us&lt;/language&gt;. 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).
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 `&lt;language&gt;en-us&lt;/language&gt;` to `&lt;language&gt;en&lt;/language&gt;` 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 &gt;= 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: **`&lt;language&gt;en-us&lt;/language&gt;`**. 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).
clawbot added 4 commits 2026-08-09 16:54:34 +02:00
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.
TEMPORARY: run the deploy build job on this branch
All checks were successful
check / check (push) Successful in 1m8s
Build and Deploy to Cloudflare Pages / build (push) Successful in 1m1s
Build and Deploy to Cloudflare Pages / deploy (push) Has been skipped
2a950232af
Dropped before merge. Exists only so act_runner really executes the
build job against the new hugo install path.
Remove the temporary deploy trigger
All checks were successful
check / check (push) Successful in 10s
f7d614952d
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.
clawbot added the needs-review label 2026-08-09 16:54:40 +02:00
clawbot self-assigned this 2026-08-09 16:54:41 +02:00
Author
Collaborator

What this builds, and what actually proved it

#26script/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.

#18hugo.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
&lt;language&gt; 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 &gt;= 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 `&lt;language&gt;` 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.
Author
Collaborator

Review: PASS

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 &lt;meta name=generator content="Hugo 0.164.0"&gt;; b→c byte-identical across the whole public/ tree. RSS &lt;language&gt;en-us&lt;/language&gt; in a, b and c — the #18 regression does not occur in the merged state; &lt;html lang=en&gt; 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, 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 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, #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 `&lt;meta name=generator content="Hugo 0.164.0"&gt;`; b→c byte-identical across the whole `public/` tree. RSS `&lt;language&gt;en-us&lt;/language&gt;` in **a, b and c** — the [#18](https://git.eeqj.de/sneak/lora.vegas/issues/18) regression does not occur in the merged state; `&lt;html lang=en&gt;` 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`.
clawbot added merge-ready and removed needs-review labels 2026-08-09 17:17:44 +02:00
Author
Collaborator

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.

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 main 2026-08-09 17:18:08 +02:00
Sign in to join this conversation.