fix(788): one declarative H10 verdict vocabulary, derived by both sides (#846)
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 6s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 25s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m17s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m50s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m22s

The verdict words lived in two hand-written shell copies — the `case` arms of
post-review-verdict.sh (write) and the POS_RE/NEG_RE regexes of
check-review-verdict.sh (read) — held together by nothing but a comment that had
already gone stale. scripts/lib/review-verdict-vocabulary.sh now declares them
once and both sides derive; neither script enumerates a verdict word any more.

Only the WORD SET moved. The grammar stays in check-review-verdict.sh, where
every #629 false-open actually lived.

No parity test: #774 shipped one and withdrew it after six rounds, because a
regex over shell source is not a shell parser. The proof is behavioural and
graded MUTATION — the harness restores the pre-#788 hardcoded POS_RE each run and
requires it to redden.

Enforcement is a DATA dependency, not a control-flow gate. Review round 1 found a
real fail-open in the first commit: `${#arr[@]}` is nounset-safe only for a
declared-empty array, and under `set -u` that error inside a function called as
`if ! validate` skips BOTH branches — so on the reader (deliberately no `set -e`)
an explicit BLOCKED @ head classified `positive`, exit 0. Validation now sets a
sentinel on its last line and the derived views refuse without it.

Six cold review rounds; rounds 2-6 found no fail-open across differential fuzzing
(4788 / 2612 / 7560 payloads, zero divergences from origin/main's grammar),
sentinel forgery, environment poisoning, declare -p evasion on bash 5.3 and 3.2,
path/symlink resolution and probe TOCTOU. Every malformation fails closed: reader
exit 2, writer exit 1 with nothing posted.

Also corrected: CLAUDE.md and release.review-verdict-gate both enumerated the
vocabulary without LGTM, a word the code has accepted since #629.

fixes #788

Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
This commit was merged in pull request #846.
This commit is contained in:
2026-08-26 20:58:09 +00:00
committed by timothy
parent ba6a4b08aa
commit 469d19852c
14 changed files with 1053 additions and 32 deletions
+4 -2
View File
@@ -429,7 +429,9 @@ def test_non_hex_head_is_an_input_error():
# completeness claim: the property below is PER-MEMBER — "each of these words classifies as exactly
# one thing" — so a word missing from this list is simply an untested word, not a defect the list
# conceals. It is emphatically NOT a claim that these are the only words the scripts accept. Proving
# THAT needs one shared vocabulary both scripts read, which is ersatztv#788.
# THAT is what the shared vocabulary in `scripts/lib/review-verdict-vocabulary.sh` supplies
# (ersatztv#788, landed); `test_review_verdict_vocabulary.py` makes the completeness claim against
# that one declaration, which is why this list may stay a per-member literal.
POSITIVE_WORDS = ["MERGEABLE", "APPROVED", "LGTM"]
NEGATIVE_WORDS = ["BLOCKED", "NOT-MERGEABLE"]
@@ -463,7 +465,7 @@ def test_each_verdict_word_retains_its_established_polarity(word, expected):
both flags and precedence decides.
WHICH DIRECTION THIS CATCHES, MEASURED RATHER THAN REASONED — the first draft of this docstring
asserted it and was checked afterwards, which is the wrong order. `check-review-verdict.sh:212`
asserted it and was checked afterwards, which is the wrong order. `check-review-verdict.sh:284`
reads `if [ "$is_pos" = 1 ]; then head_pos=1; else head_neg=1; fi`, so `is_pos` wins per line
and an overlapping word resolves POSITIVE. Both mutations were run against this test: