From 5a12ae793058cd0d76f89bf96eec9bbe09219776 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Wed, 9 Sep 2026 04:09:08 +0700 Subject: [PATCH] charter: test a refusal, and do not count a transport failure as one Step 8 gained a refusal paragraph in 2f71a30, which said what a lead does once the forge refuses a merge. It did not say how a lead establishes that it was refused. Both halves of this amendment come from the fleet01 lead, measured on akb/kb on 2026-09-08 UTC, and both are ways to be wrong about a permission you never tested. Do not read a refusal off a permissions field. After the operator granted merge rights, the lead re-ran its probe: POST .../pulls/53/merge with an all-zeroes head_commit_id, chosen so the request cannot succeed on its merits and a rejection can only mean the refusal. It returned 409 'head out of date' where the identical request returned 405 'User not allowed to merge PR' on 2026-09-06. A 409 is payload validation and sits after the permission gate, so the grant took. The lead reports the repository permissions object did not change across that flip -- still admin:false, push:true, pull:true. I did not read that object myself; my forge token is a different identity and would return a different one, so this stays the lead's measurement and not mine. Merge rights on a protected branch live in branch protection, so a permissions field can be wrong in both directions. Do not count a transport failure as a refusal. The lead's first attempt returned HTTP 000, because GITEA_HOST already carries a scheme and a trailing slash and the URL came out as https://https://git.ltms.dev//api/... Under a 'not 200' test that is indistinguishable from being refused. A probe exists to separate a refusal from everything else, so an error that never reached the gate has to be a third answer that concludes nothing. Propagated to the wiki template in the same turn, wiki 8c2ef96 on main; the sync check in this file's addendum reports 'in sync: True'. --- CLAUDE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 2545d5f..81005a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,6 +100,12 @@ below are the procedure — run them in order, every task, not only the big ones without having read the diff yourself. A refusal is exactly when that shortcut is tempting, because no action is left that forces you to look, and taking it turns this step into forwarding a reviewer's verdict — which is delegating the merge by proxy, two lines above. + **Test a refusal; do not read it off a permissions field.** A protected branch holds its merge + rights separately from the repository permissions, so that field can say yes while the merge is + refused, and still say no after a grant makes it work. Probe instead, with a request that cannot + succeed on its merits, so a rejection can only mean the refusal. Treat a transport failure as a + third answer that proves nothing: a timeout, a DNS error or a bad URL is not a refusal, and + counting it as one makes you sure of something you never measured. **Steps 3 and 4 are separate on purpose** — spawning and sending in one loop is how parallel work silently becomes serial, and it is the most common way this layer is wasted. For the same reason,