From 985a6e150511a2eabafdbb685d97b9d396dfe3b1 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 5 Sep 2026 00:45:36 +0200 Subject: [PATCH] =?UTF-8?q?fix(885):=20a=20challenge-less=20401=20lands=20?= =?UTF-8?q?in=20the=20TOKEN-LEG=20arm,=20not=20the=20never-asked=20one=20?= =?UTF-8?q?=E2=80=94=20the=20table=20and=20the=20comment=20said=20otherwis?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row added for the third refusal shape was written as "401 / 403 carrying NO `Www-Authenticate` challenge at all", and the script comment beside it made the same binding. Both are wrong for the 401 half: `probe` enters the token leg on a 401, so a challenge-less 401 DOES call `acquire_token`, which sets `token_leg_done=1` and abandons for want of a realm — it reports `could NOT OBTAIN an anonymous pull token`, the row above. Only a FIRST-READ 403 reaches the never-asked arm. The parametrised test already drives both codes and asserts exactly that split; the prose beside them did not match it. The three rows now bind one shape each: a refusal surviving a bearer the run really obtained, a 401 whose token leg yielded none (no challenge header, no realm, or no token in the answer), and a first-read 403 that asked for nothing. Prose between arms regenerates mis-bindings — which is why the arms are stated as one self-binding row apiece rather than as a category sentence covering two. Refs #885 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV --- docs/ci-cd.md | 6 +++--- scripts/ci-toolchain-image-resolves.sh | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 3e7fd56b9..e73683619 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -2452,9 +2452,9 @@ because they send you to different places: | HTTP 200 with a manifest body | green | resolves | | HTTP 404 | **red** | `IS GONE` — rebuild the tag (recovery above) | | HTTP 200, body is not a manifest | **red** | something is answering for the registry (proxy, login page) | -| 401 / 403 after a token was obtained | **red** | the registry `refused an ANONYMOUS read` — check that `timothy/ersatztv` and its `ersatztv-ci` package are still PUBLIC | -| the challenge names no realm, or the token endpoint answers with no token | **red** | `could NOT OBTAIN an anonymous pull token` — worded apart on purpose: this is about the registry's token endpoint, not the package's visibility. Not retried | -| 401 / 403 carrying NO `Www-Authenticate` challenge at all | **red** | `refused an ANONYMOUS read ... WITHOUT issuing a Bearer challenge` — no token was ever requested, because `probe` enters the token leg on a `401` only. An answer about ACCESS to the registry (visibility, or something answering for it) | +| 401 / 403 that survives a bearer the run actually obtained | **red** | the registry `refused an ANONYMOUS read ... even after a Bearer token was obtained` — about the PACKAGE: check that `timothy/ersatztv` and its `ersatztv-ci` package are still PUBLIC | +| 401 whose token leg yielded no bearer — no `Www-Authenticate` at all, a challenge naming no realm, or a token endpoint answering with no token | **red** | `could NOT OBTAIN an anonymous pull token` — about the registry's TOKEN ENDPOINT, not the package's visibility. Not retried | +| 403 on the FIRST read, before any token leg — `probe` enters it on a `401` only, so nothing was ever asked for | **red** | `refused an ANONYMOUS read ... WITHOUT issuing a Bearer challenge` — about ACCESS to the registry: visibility, or something answering for it | | anything else (5xx, unreachable, no `curl`) | **red** after `ETV_CI_ATTEMPTS` tries | `could NOT VERIFY` — check the registry's health, NOT the pin | **The three refusal rows say only what actually ran**, and that is the point of wording them apart diff --git a/scripts/ci-toolchain-image-resolves.sh b/scripts/ci-toolchain-image-resolves.sh index 95abe988a..edf8265de 100755 --- a/scripts/ci-toolchain-image-resolves.sh +++ b/scripts/ci-toolchain-image-resolves.sh @@ -202,10 +202,10 @@ for pin in $pins; do # contexts; # * a challenge that yielded no usable token is an infrastructure answer about the TOKEN # ENDPOINT; - # * a refusal carrying NO Bearer challenge at all never reached the token leg — `probe` - # enters it on a 401 only, so a first-read 403 (or a 401 with no `Www-Authenticate`, which - # the token leg then abandons) leaves `token` empty having asked for nothing. This is an - # answer about ACCESS to the registry, and the branch order below is `token` first + # * a FIRST-READ 403 never reached the token leg at all — `probe` enters it on a 401 only — + # so nothing was ever asked for. This is an answer about ACCESS to the registry. Note the + # boundary: a 401 carrying no `Www-Authenticate` DOES enter the token leg and abandon it, + # so it lands in the row above, not this one. The branch order below is `token` first # precisely so this case cannot borrow either of the other two mechanisms. # # A failed token leg is NOT retried, deliberately: `token_leg_done` is set before the attempt