fix(docs): correct the Hub size claims, and gate them so they cannot rot again
Lint / hadolint (push) Successful in 9s
Lint / doc-drift (push) Successful in 13s
Lint / skill-floor (push) Successful in 15s
Lint / actionlint (push) Successful in 18s

DOCKER_HUB.md claimed ~1.1 GB for :latest while Docker Hub served 1.37 GB --
20% wrong, drifting quietly across eight releases, and POSTed to Docker Hub by
update-description every time. It is the first number a stranger reads about
this image.

Root cause is structural, not carelessness: every OTHER claim check-doc-drift.sh
guards is anchored to a build file (a pin, an ARG, a placeholder), so it cannot
rot without someone editing the thing it describes. Nothing in this repo states
the image size, so nothing measured it.

Corrected against measured full_size after v1.9.2 published (amd64/arm64):
  :latest         ~1.1  -> ~1.23 GB   (1.228 / 1.211)
  :latest-studio  ~1.15 -> ~1.25 GB   (1.255 / 1.238)
  :base-latest    ~1.0  -> ~1.17 GB   (1.167 / 1.151)
full_size tracks the FIRST manifest entry (amd64), not the sum across arches --
measured: full_size=1.228, amd64=1.228, arm64=1.211, sum=2.439 -- which is what
the table's per-arch "Size (compressed)" column claims.

New check 8 gates those claims against Hub. Fails on drift; SKIPS LOUDLY via a
new skip() helper (counted, named in the summary) when curl/python3 are absent,
the API is unreachable, or SKIP_SIZE_CHECK=1. A skip is deliberately neither OK
nor a failure: printing an unverified claim as OK is the habit this file exists
to break, and failing on Docker Hub's uptime would hold releases hostage to a
third party. Not wired into hooks/pre-push, so pushes stay offline.

Two bugs caught by writing the expected exit code down before running the check:

  1. The tolerance would have missed its own motivating case. The percentage is
     computed against the MEASURED size, but the 20% first chosen came from the
     claim-relative figure; the real drift was |1.1-1.37|/1.37 = 19.7% and would
     have passed. Now 15%, inside a window with both bounds measured: above the
     largest legitimate skew (11.4%, a claim describing the published release
     while the next tag changes the size) and below the rot it must catch.

  2. A `|| true` on the python invocation made the gate FAIL OPEN -- it printed
     DRIFT and exited 0. Removed; the outer `|| SIZE_RC=$?` satisfies set -e
     without swallowing the code. Verified two-sided: 19% drift exits 1 at the
     default tolerance and 0 at SIZE_TOLERANCE_PCT=25, so the threshold does the
     work rather than the ordering.

NOT covered, and the script says so where a reader will see it: README.md's
~3.2 GB figures are UNCOMPRESSED and the registry exposes compressed sizes only,
so measuring them needs a real pull. A green check 8 says nothing about them.

Verified: check-doc-drift.sh rc=0 on the corrected tree, rc=1 on injected drift,
rc=0 with --warn-only, SKIP+rc=0 on the offline path (proxy to a dead port);
shellcheck clean at default severity (the sed single-quote SC2016 carries a
disable with its reason); lint-shell.sh 16 files clean.
This commit is contained in:
2026-09-14 20:33:40 +02:00
parent f5c53b8693
commit b8d818ed99
3 changed files with 212 additions and 5 deletions
+60
View File
@@ -11,6 +11,66 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
---
## Unreleased
**The size numbers on the Docker Hub page were the only claim in these docs with
nothing in the repo to check them against, and they had gone 20% wrong across
eight releases.** Every other claim `scripts/check-doc-drift.sh` guards is
anchored to a build file — a pin, an `ARG`, a placeholder — so it cannot rot
without someone editing the thing it describes. Nothing in this repo states the
image size, so `DOCKER_HUB.md`'s `~1.1 GB` simply drifted while the image grew,
and `update-description` POSTed it to Docker Hub every release. It is the first
number a stranger reads about this image.
Corrected against Docker Hub's measured `full_size`, 2026-09-14 after v1.9.2
published (amd64 / arm64, compressed):
| Row | Claimed | Measured | Now says |
|---|---|---|---|
| `:latest` | ~1.1 GB | 1.228 / 1.211 | ~1.23 GB |
| `:latest-studio` | ~1.15 GB | 1.255 / 1.238 | ~1.25 GB |
| `:base-latest`, `:base-<hash>` | ~1.0 GB | 1.167 / 1.151 | ~1.17 GB |
`full_size` is the right field because it tracks the **first manifest entry**
(amd64), not the sum across architectures — measured on v1.9.2:
`full_size=1.228`, `amd64=1.228`, `arm64=1.211`, `sum=2.439`. That matches the
table's per-arch "Size (compressed)" column.
**New check 8 in `scripts/check-doc-drift.sh`: size claims vs Hub's measured
`full_size`,** so this class cannot rot silently again. It fails on drift beyond
tolerance, and **skips loudly** — a new `skip()` helper, counted and named in the
summary — when `curl`/`python3` are missing, the API is unreachable, or
`SKIP_SIZE_CHECK=1`. Skips are deliberately neither `OK` nor a failure: printing
an unverified claim as OK is the habit this file exists to break, while failing
on Docker Hub's uptime would make every release hostage to a third party. Not in
`hooks/pre-push` (that runs `lint-shell.sh` only), so pushes do not hit the
network.
Two bugs were caught while building it, both by writing the expected exit code
down *before* running the check:
- **The tolerance would have missed its own motivating case.** The percentage is
computed against the *measured* size, but the 20% first chosen came from the
claim-relative figure. The real drift was `|1.1 − 1.37| / 1.37 = 19.7%` — it
would have passed. Now 15%, sitting inside a window whose bounds are both
measured: above the largest legitimate skew (a claim describing the published
release while the next tag changes the size — v1.9.1's 1.37 against v1.9.2's
1.23 = 11.4%) and below the rot it exists to catch (19.7%).
- **A `|| true` on the python invocation made the gate fail open.** It printed
`DRIFT` and exited 0 — a gate that reports the defect and passes anyway.
Removed; the outer `|| SIZE_RC=$?` is what satisfies `set -e` without
swallowing the code. Verified two-sided afterwards: a 19% drift exits 1 at the
default tolerance and 0 at `SIZE_TOLERANCE_PCT=25`, so the threshold is doing
the work rather than the ordering.
**Explicitly NOT covered:** `README.md`'s `~3.2 GB` figures are *uncompressed*
on-disk sizes, and the registry exposes compressed sizes only (manifest layer
sizes are compressed; the config blob carries no uncompressed totals). Measuring
them needs a real pull, so they remain unverified — a green check 8 says nothing
about them, and the script says so where a reader will see it.
---
## v1.9.2 — 2026-09-14
**The v1.9.1 residual is attributed and fixed: it was mostly npm's own download