fix(778): the 200-not-an-array arm had the same lying reason, one branch earlier
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 31s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 43s
PR Gates / Docs update reminder (pull_request) Successful in 48s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 55s
Review verdict / Set review-verdict status (pull_request_target) Successful in 16s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m1s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m59s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m51s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 5s
review-verdict/h10 Review-verdict: MERGEABLE @ 7347a08 (base: main)

Local review returned MERGEABLE — no Blocker, High or Medium, both sentinels verified
pinned by mutation, no regressions. Its one Low is taken rather than deferred, because
it is a one-line fix and because deferring it would leave exactly the shape this PR
exists to document.

A 200 whose body is NOT an array never reaches the classifier: the array gate diverts
it, `bp_code` stays 200, and the generic ask then reported "HTTP '200' — Gitea
unreachable, or these credentials lack the repo-admin scope" about a read that plainly
succeeded. That is the identical defect the previous commit fixed for the
throw-inside-the-classifier arm, one branch earlier — fixed where it was noticed, left
in its twin.

The previous commit's message even generalised the pattern ("a sentinel that doubles as
an HTTP code makes a decision state a cause that did not happen") while covering only
one of the two arms it applies to. The test is now parametrised over all three shapes
that reach an unusable 200 — UNPARSEABLE-RULES, GARBAGE, EMPTY — and reverting the new
sentinel reddens the two that the first fix missed.

Also finishes the de-indent the previous commit claimed: that comment block went from
19 leading spaces to 6 while its siblings use 2, so the claim was true of the direction
and not of the result.

729 tests green.

refs #778

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-19 22:50:23 +02:00
co-authored by Claude Opus 5
parent 3f0af38509
commit 7347a08979
2 changed files with 20 additions and 4 deletions
+11 -2
View File
@@ -440,7 +440,7 @@ else
# without knowing the precedence rules at all, which is the only claim this
# code is entitled to make about somebody else's resolver.
#
# Case folding is ASCII-only here, while Gitea's `EqualFold` is
# Case folding is ASCII-only here, while Gitea's `EqualFold` is
# Unicode-aware — so a rule `ünstable` and a base `Ünstable` fold equal there
# and not here. ASCII-fold equality implies EqualFold equality, so the gap can
# only MISS a match, never invent one; but a miss lands on `none`, which
@@ -499,7 +499,16 @@ else
*) bp_code=unreadable-rules; bp="" ;;
esac
else
bp_code=${bp_code:-000} # unreadable list -> the ask arm below
# A 200 whose body is NOT an array never reaches the classifier — it is diverted by the array
# gate above — so it needs the same sentinel, or the generic ask below reports
# "HTTP '200' — Gitea unreachable" about a read that plainly succeeded. Same defect as the
# throw-inside-the-classifier arm, one branch earlier; fixing only the arm where it was noticed
# is the twin-missed shape this PR is largely about.
if [ "$bp_code" = "200" ]; then
bp_code=unreadable-rules
else
bp_code=${bp_code:-000} # a real transport/HTTP failure -> the ask arm below
fi
bp=""
fi
rm -f "$bp_file"
@@ -807,15 +807,22 @@ def test_an_HTTP_404_on_the_LIST_read_asks_and_does_not_claim_the_list_was_read(
"a 404 read claimed the full rule list had been read and matched nothing")
def test_a_200_the_classifier_cannot_PARSE_asks_without_blaming_the_transport(hook):
@pytest.mark.parametrize("shape", ["UNPARSEABLE-RULES", "GARBAGE", "EMPTY"])
def test_a_200_the_classifier_cannot_PARSE_asks_without_blaming_the_transport(hook, shape):
"""The twin of the `nomatch` fix, on the other arm — and pinned this time rather than assumed.
A rule whose `branch_name` is a number makes the classifier throw on a read that plainly
succeeded. Mapping that to `bp_code=000` produced "could not read … (HTTP '000' — Gitea
unreachable…)", stating a transport cause for a 200. The decision (ask) was always safe; only
the reason lied, which is precisely the defect corrected one arm over for the deny.
Parametrised over all three shapes that reach a 200 the hook cannot use, because the first fix
covered only `UNPARSEABLE-RULES` — the arm where it was noticed. `GARBAGE` (an object, not an
array) and `EMPTY` are diverted one branch EARLIER, by the array gate, and kept `bp_code=200`,
so they reported "HTTP '200' — Gitea unreachable" about a successful read. Fixing one arm and
leaving its twin is the shape this PR is largely about.
"""
hook.set_branch_protection("UNPARSEABLE-RULES")
hook.set_branch_protection(shape)
reason = hook.reason()
assert "ask" in reason, "an unparseable rule list produced a decision instead of a question"
assert "could not parse" in reason, (