chore(process): #303 H10 — review-verdict merge-gate (latest commit must be reviewed) #306

Merged
timothy merged 4 commits from ci/303-h10-review-verdict-gate into main 2026-07-12 19:32:00 +02:00
Owner

Part of #303 (process hardening). Ships the last Wave-3 hook, H10.

What

A PR merge is allowed only when a Review-verdict: comment references the PR's current head sha — proving the latest commit was reviewed, not a stale earlier diff. Mechanizes the ersatztv#242 lesson ("re-review the fix commit, not just the initial PR diff").

Folds into the existing H6 pretooluse-merge-consent.sh as condition (c) — reusing its PR fetch, docs-only exemption, and Gitea-auth-from-env, so there's one detection path, no drift (per the #303 methodology review). No new hook file, no settings.json change (already wired on the merge tool).

Convention

After an adversarial/Codex review of a PR (or its latest fix commit), post a PR comment with a line:

Review-verdict: <MERGEABLE|APPROVED|BLOCKED|NOT-MERGEABLE> @ <head-sha>

Decision tiers (mirror H6's graceful adoption)

State Decision
positive verdict @ current head allow
verdict @ an older sha (stale review) deny — the #242 failure mode
head verdict is negative (BLOCKED) deny
Review-verdict: marker but no sha / none yet ask
comments unfetchable / head sha unresolvable ask

Scope: the Claude PreToolUse gate on the Gitea merge tool only (a direct git push origin main has no PR comments; docs-only PRs stay exempt via H6's file-set exemption).

Verification

Pipe-tested 12 cases end-to-end against the real hook via a fake curl on PATH (positive@head short/full sha, stale@old-sha, negative@head, marker-no-sha, quoted-convention-no-sha, comments-fetch-fail, docs-only-exempt, H6 unticked-Done-when regression, non-merge passthrough, positive-among-noise). bash -n clean. All green.

Docs

  • docs/decisions.md — new H10 entry + TOC line (pure append; passes the H9 append-only guard without [decisions-edit]).
  • CLAUDE.md — Task Completion Protocol updated to describe condition (c) + the Review-verdict: convention.

This PR is docs/process-only → exempt from the merge gate itself. Adversarial review + a dogfooded Review-verdict: comment to follow.

🤖 Generated with Claude Code

Part of #303 (process hardening). Ships the last Wave-3 hook, **H10**. ## What A PR merge is allowed only when a `Review-verdict:` comment references the PR's **current head sha** — proving the *latest* commit was reviewed, not a stale earlier diff. Mechanizes the ersatztv#242 lesson ("re-review the fix commit, not just the initial PR diff"). Folds into the existing H6 `pretooluse-merge-consent.sh` as condition (c) — reusing its PR fetch, docs-only exemption, and Gitea-auth-from-env, so there's **one detection path, no drift** (per the #303 methodology review). No new hook file, no settings.json change (already wired on the merge tool). ## Convention After an adversarial/Codex review of a PR (or its latest fix commit), post a PR comment with a line: ``` Review-verdict: <MERGEABLE|APPROVED|BLOCKED|NOT-MERGEABLE> @ <head-sha> ``` ## Decision tiers (mirror H6's graceful adoption) | State | Decision | |---|---| | positive verdict @ current head | **allow** | | verdict @ an older sha (stale review) | **deny** — the #242 failure mode | | head verdict is negative (BLOCKED) | **deny** | | `Review-verdict:` marker but no sha / none yet | **ask** | | comments unfetchable / head sha unresolvable | **ask** | Scope: the Claude PreToolUse gate on the Gitea merge tool only (a direct `git push origin main` has no PR comments; docs-only PRs stay exempt via H6's file-set exemption). ## Verification Pipe-tested 12 cases end-to-end against the **real** hook via a fake `curl` on PATH (positive@head short/full sha, stale@old-sha, negative@head, marker-no-sha, quoted-convention-no-sha, comments-fetch-fail, docs-only-exempt, H6 unticked-Done-when regression, non-merge passthrough, positive-among-noise). `bash -n` clean. All green. ## Docs - `docs/decisions.md` — new H10 entry + TOC line (pure append; passes the H9 append-only guard without `[decisions-edit]`). - `CLAUDE.md` — Task Completion Protocol updated to describe condition (c) + the `Review-verdict:` convention. This PR is docs/process-only → exempt from the merge gate itself. Adversarial review + a dogfooded `Review-verdict:` comment to follow. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 1 commit 2026-07-12 18:54:49 +02:00
chore(process): #303 H10 — review-verdict merge-gate (latest commit must be reviewed)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m43s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
9fd8f40541
Folds condition (c) into pretooluse-merge-consent.sh (H6): a PR merge is
allowed only when a `Review-verdict:` comment references the PR's CURRENT
head sha — proving the latest commit was reviewed, not a stale earlier diff
(mechanizes the ersatztv#242 "re-review the fix commit" lesson).

Graceful adoption mirrors H6's Done-when tiering:
- positive verdict @ head        -> allow
- verdict @ older sha (stale)    -> deny  (#242 failure mode)
- head verdict negative          -> deny
- marker with no sha / none yet  -> ask
- comments unfetchable           -> ask

Reuses H6's PR fetch, docs-only exemption, and Gitea-auth-from-env (one hook,
no detection drift — per the #303 methodology review). Pipe-tested 12 cases.

Docs: decisions.md (new H10 entry + TOC), CLAUDE.md Task Completion Protocol.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy added 1 commit 2026-07-12 19:02:17 +02:00
fix(process): #303 H10 — anchor sha match to the @<sha> field; retraction-wins [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m13s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
f87a2092c4
Adversarial review found false-opens in the first cut:
- `grep -F "$short"` was an unanchored substring test: a MERGEABLE verdict for
  a DIFFERENT/older commit was accepted whenever the head 7-prefix appeared
  anywhere on the line (inside a longer sha, or an unrelated commit URL). Now
  each verdict line's `@ <sha>` token is extracted and matched to head by git
  short-sha prefix semantics (head begins with token, token >=7 chars).
- No retraction semantics: a later `BLOCKED @ head` didn't override an earlier
  `MERGEABLE @ head`. Now a negative verdict on head wins -> deny.
- A 7-digit build number falsely tripped the "references an older commit" deny;
  the `@`-anchored parse fixes it -> a marker with no @sha now asks, not denies.

Also documents the issue-comment scope (gate reads issues/{pr}/comments, not
Gitea formal-review bodies). Pipe-tested: 16 cases incl. 4 adversarial
false-open reproductions, all now deny/ask. bash -n + shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy added 1 commit 2026-07-12 19:11:21 +02:00
fix(process): #303 H10 — anchor verdict marker to line-start (close self-reference false-open) [decisions-edit]
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m59s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m13s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
ff3df39c43
Second adversarial re-review (of the fix commit itself — dogfooding H10) found
a remaining false-open: the `review-verdict:` marker was matched anywhere on a
line, so a comment merely QUOTING the positive template — an instruction
("please post: Review-verdict: MERGEABLE @ <head>"), a blocking comment
explaining how to clear itself, or the gate's OWN suggestion text echoed into a
comment — was classified as a real head verdict and self-approved the merge.

Fix: anchor the marker to line-start (`^[[:space:]]*review-verdict:`). A real
verdict line starts with the marker; quoted/instructional uses have text before
it. Also drops the dead `nosha` var (SC2034).

Finding 2 (a BLOCKED mis-anchored to an OLDER sha doesn't retract a
MERGEABLE@head) is deliberately NOT "fixed": staleness is symmetric — a
pre-fix BLOCKED@oldsha must not block forever after the fix changes the sha and
earns a fresh MERGEABLE@head. To retract, re-review head and post BLOCKED@head.
Documented in decisions.md.

Pipe-tested 21 cases (5 new: instructional-quote, self-reference reason-text,
line-start-in-multiline, leading-whitespace, blocking-quotes-template), all
deny/ask/allow correct. bash -n + shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy added 1 commit 2026-07-12 19:14:53 +02:00
fix(process): #303 H10 — anchor is_pos to the leading marker (monotonic hardening)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 13s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
938733c3d0
Final re-review returned SHIP-IT with one contrived, pre-existing residual: a
line starting `Review-verdict: BLOCKED …` that ALSO contains a second literal
`review-verdict: mergeable` substring later on the same line read as positive.
Anchor the is_pos check to line-start so only the line's OWN leading verdict
word counts. Safe-by-construction: anchoring a positive matcher can only REDUCE
the allow-surface, so it cannot introduce a false-open (the dangerous
direction); the 21-case regression confirms no false-deny (all still
allow/deny/ask as before) + a new B6 case for this exact vector. No further
review round needed — the change is monotonic. shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Review-verdict: MERGEABLE @ 938733c3

Dogfooding the convention this PR ships. Three independent adversarial review rounds (one per fix commit — the #242 discipline H10 encodes):

  • R1 found 3 false-opens (unanchored grep -F "$short" substring sha match; no retraction semantics; false-deny on 7-digit build numbers) → fixed by @ <sha>-field parsing + git short-sha prefix matching + negative-verdict-wins.
  • R2 (re-review of the R1 fix) found the self-reference false-open (a comment quoting the positive template — including the gate's own suggestion text — self-approved) → fixed by anchoring the marker to line-start.
  • R3 (re-review of the R2 fix) returned SHIP-IT; the one contrived pre-existing residual (double-marker on one BLOCKED line) closed by anchoring is_pos too (monotonic, safe-by-construction).

Verification: 22 pipe-tested cases incl. 6 adversarial false-open reproductions, all deny/ask/allow correct; bash -n + shellcheck clean. Non-blocking accepted residual: markdown-formatted verdicts (**bold**, - list) fall to ask (safe) — the plain line-start convention is documented in decisions.md.

Note: this PR is docs/process-only → exempt from the H6/H10 gate itself; verdict posted for demonstration.

Review-verdict: MERGEABLE @ 938733c3 Dogfooding the convention this PR ships. Three independent adversarial review rounds (one per fix commit — the #242 discipline H10 encodes): - **R1** found 3 false-opens (unanchored `grep -F "$short"` substring sha match; no retraction semantics; false-deny on 7-digit build numbers) → fixed by `@ <sha>`-field parsing + git short-sha prefix matching + negative-verdict-wins. - **R2** (re-review of the R1 fix) found the self-reference false-open (a comment *quoting* the positive template — including the gate's own suggestion text — self-approved) → fixed by anchoring the marker to line-start. - **R3** (re-review of the R2 fix) returned SHIP-IT; the one contrived pre-existing residual (double-marker on one BLOCKED line) closed by anchoring `is_pos` too (monotonic, safe-by-construction). Verification: 22 pipe-tested cases incl. 6 adversarial false-open reproductions, all deny/ask/allow correct; `bash -n` + shellcheck clean. Non-blocking accepted residual: markdown-formatted verdicts (`**bold**`, `- list`) fall to `ask` (safe) — the plain line-start convention is documented in decisions.md. Note: this PR is docs/process-only → exempt from the H6/H10 gate itself; verdict posted for demonstration.
timothy merged commit 2879c012ba into main 2026-07-12 19:32:00 +02:00
timothy deleted branch ci/303-h10-review-verdict-gate 2026-07-12 19:32:00 +02:00
Sign in to join this conversation.