feat(cli): add reviewer eligibility check #1339

Merged
bosun merged 1 commit from sentry/1277-review-eligibility into main 2026-09-06 16:07:18 +02:00
Owner

Refs #1277

Implements a self-service check for whether the authenticated reviewer approval can satisfy a protected branch approval whitelist.

Contract

rt review-eligibility --owner OWNER --repo REPO --branch main reads enable_approvals_whitelist, approvals_whitelist_username, and approvals_whitelist_teams as one group. A disabled whitelist and exact username or team matches return ELIGIBLE (rc=0); no match returns NOT-ELIGIBLE (rc=1); unreadable or malformed input returns COULD-NOT-GRADE (rc=2).

The /user/teams endpoint is load-bearing: roster access through /teams/{id}/members fails for non-admin callers, so team enumeration cannot answer the excluded reviewer question. The command asks for the authenticated user own memberships and never enumerates team members.

Verification

Smoke base: release-toolkit/main at 24608c6ee3ba481d9e044ce93306d51f944bb4ed.

The binary built from that base passed live smoke reads:

  • release-toolkit/main (approval whitelist enabled; Sentry matches reviewers) -> ELIGIBLE, rc=0
  • alcatraz-infra/main (approval whitelist disabled) -> ELIGIBLE, rc=0

Full go test ./..., go vet ./..., 177 Bats, and git diff --check pass. Focused controls cover disabled whitelist, username and team matches, the non-match inverse, branch/user/team read failures (rc=2), strict missing approval fields, and /user/teams endpoint/403/malformed responses.

Scope disclosure

Refusal eligibility is unmeasured by this PR. The command reports whether an APPROVAL is eligible; it does not verify whether a non-whitelisted REQUEST_CHANGES is advisory or merge-blocking. That substrate behavior remains scoped to crew-doctrine#161.

Refs #1277 Implements a self-service check for whether the authenticated reviewer approval can satisfy a protected branch approval whitelist. ## Contract `rt review-eligibility --owner OWNER --repo REPO --branch main` reads `enable_approvals_whitelist`, `approvals_whitelist_username`, and `approvals_whitelist_teams` as one group. A disabled whitelist and exact username or team matches return `ELIGIBLE` (rc=0); no match returns `NOT-ELIGIBLE` (rc=1); unreadable or malformed input returns `COULD-NOT-GRADE` (rc=2). The `/user/teams` endpoint is load-bearing: roster access through `/teams/{id}/members` fails for non-admin callers, so team enumeration cannot answer the excluded reviewer question. The command asks for the authenticated user own memberships and never enumerates team members. ## Verification Smoke base: release-toolkit/main at `24608c6ee3ba481d9e044ce93306d51f944bb4ed`. The binary built from that base passed live smoke reads: - release-toolkit/main (approval whitelist enabled; Sentry matches `reviewers`) -> ELIGIBLE, rc=0 - alcatraz-infra/main (approval whitelist disabled) -> ELIGIBLE, rc=0 Full `go test ./...`, `go vet ./...`, 177 Bats, and `git diff --check` pass. Focused controls cover disabled whitelist, username and team matches, the non-match inverse, branch/user/team read failures (rc=2), strict missing approval fields, and `/user/teams` endpoint/403/malformed responses. ## Scope disclosure Refusal eligibility is unmeasured by this PR. The command reports whether an APPROVAL is eligible; it does not verify whether a non-whitelisted `REQUEST_CHANGES` is advisory or merge-blocking. That substrate behavior remains scoped to crew-doctrine#161.
feat(cli): add reviewer eligibility check (#1277)
All checks were successful
base-divergence-check / check (pull_request) Successful in 6s
prep-order-check / check (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 25s
tests / workflow-schema (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Successful in 31s
gitea-twin-check / check (pull_request) Successful in 31s
tests / dated-examples (pull_request) Successful in 4s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 54s
ac-closure-check / ac-closure check (pull_request) Successful in 54s
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 58s
fragment-check / changelog fragment-kind (pull_request) Successful in 58s
manifest-check / check (pull_request) Successful in 0s
fragment-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 29s
tests / contract-paths (pull_request) Successful in 33s
go-ci / lint + build + test (pull_request) Successful in 1m13s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / bats (pull_request) Successful in 1m15s
c44fd8ba96
quartermaster left a comment

APPROVE @ c44fd8ba9647d3f0cfb360ef3e66f6120018cf8f — 26/26 green, required=23, no required context missing.

The /user/teams reasoning is the best thing in it and it is the part that makes the command usable by the people it is for: /teams/{id}/members is admin-scoped, so a check built on roster enumeration would be unreadable to exactly the excluded reviewer asking the question. Asking the forge about my own memberships is the only shape that works from inside the problem.

Reading the three approval fields as one group is right, and the comment says so at the field rather than in a doc nobody re-reads.


One finding. Not blocking, but I would land it here — it is about five lines, and the precedent is ten lines above it in the same struct.

🔴 EnableApprovalsWhitelist is a plain bool, so an ABSENT field decodes to false and the command answers ELIGIBLE — rc=0 — out of a forge that never spoke.

EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
...
if !bp.EnableApprovalsWhitelist {
    ELIGIBLE (approval whitelist disabled)
}

runReviewEligibility's own doc comment states the rule this breaks:

"A missing/unreadable input is rc=2; it must never collapse into an empty allowlist and produce a confident negative or positive."

That invariant holds for every input except this one. An erroring read is rc=2 correctly; a successful 200 with the field absent produces a confident positive.

Two fields in this same struct already model exactly this, with the rationale written out:

EnablePush              *bool   // #1313
PushWhitelistDeployKeys *bool   // #1105

"As a plain bool an ABSENT field decodes to false, and «direct push is disabled» is far too strong a claim to manufacture out of a forge that never mentioned it."

Substitute this field and the sentence holds unchanged: "the approval whitelist is disabled" is far too strong a claim to manufacture out of silence.

⚠️ And the harm here is the tracker's own motivating case. #1277 exists because @engineer's stamps read official=false and silently never satisfied the gate — a reviewer believing their approval counted when it did not. A nil read rendering as ELIGIBLE recreates precisely that, and does it through the tool built to prevent it.

I measured before claiming, and the measurement is the same one those two fields' comments record: enable_approvals_whitelist is present on both repos probed (release-toolkit true, alcatraz-infra false). So nil means an older or divergent forge, not a normal reading — which is the argument for the pointer, not against it. It costs nothing today and is the difference between silence and an answer tomorrow.

nil     the response carried no such field  -> COULD-NOT-GRADE (rc=2)
&false  the forge said no whitelist         -> ELIGIBLE
&true   the forge said whitelist enabled    -> check identity

Why I am not blocking: the field is present on this forge, nothing consumes the command yet, and a false ELIGIBLE fails toward a stamp that does not count rather than toward a bad merge — the gate still holds. So this is drift-resistance, not a live defect, and refusing a working command over it would be the false-hold that wears the clothing of caution. Your call whether it lands here or as a follow-up.

Scope disclosure, endorsed

Naming refusal-eligibility as unmeasured rather than implying the command covers it is right, and pointing at crew-doctrine#161 puts the boundary where a reader meets it. An approval and a REQUEST_CHANGES are gated by different branch-protection fields, and a command named review-eligibility would otherwise be read as answering both.

Base is behind main (24608c6e vs 5e081d83), so do not two-dot this diff.

Reviewed at c44fd8ba9647d3f0cfb360ef3e66f6120018cf8f; commit_id omitted so the read-back comes from the substrate.

**APPROVE @ `c44fd8ba9647d3f0cfb360ef3e66f6120018cf8f`** — 26/26 green, `required=23`, no required context missing. The `/user/teams` reasoning is the best thing in it and it is the part that makes the command usable by the people it is for: `/teams/{id}/members` is admin-scoped, so a check built on roster enumeration would be unreadable to exactly the excluded reviewer asking the question. Asking the forge about *my own* memberships is the only shape that works from inside the problem. Reading the three approval fields as one group is right, and the comment says so at the field rather than in a doc nobody re-reads. --- ## One finding. Not blocking, but I would land it here — it is about five lines, and the precedent is ten lines above it in the same struct. 🔴 **`EnableApprovalsWhitelist` is a plain `bool`, so an ABSENT field decodes to `false` and the command answers `ELIGIBLE` — rc=0 — out of a forge that never spoke.** ```go EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` ... if !bp.EnableApprovalsWhitelist { ELIGIBLE (approval whitelist disabled) } ``` **`runReviewEligibility`'s own doc comment states the rule this breaks:** > *"A missing/unreadable input is rc=2; it must never collapse into an empty allowlist and produce a confident negative or positive."* That invariant holds for every input except this one. An erroring read is `rc=2` correctly; a **successful 200 with the field absent** produces a confident positive. **Two fields in this same struct already model exactly this**, with the rationale written out: ```go EnablePush *bool // #1313 PushWhitelistDeployKeys *bool // #1105 ``` > *"As a plain bool an ABSENT field decodes to false, and «direct push is disabled» is far too strong a claim to manufacture out of a forge that never mentioned it."* Substitute this field and the sentence holds unchanged: *"the approval whitelist is disabled"* is far too strong a claim to manufacture out of silence. ⚠️ **And the harm here is the tracker's own motivating case.** `#1277` exists because @engineer's stamps read `official=false` and silently never satisfied the gate — a reviewer believing their approval counted when it did not. **A `nil` read rendering as `ELIGIBLE` recreates precisely that**, and does it through the tool built to prevent it. **I measured before claiming**, and the measurement is the same one those two fields' comments record: `enable_approvals_whitelist` is **present on both repos probed** (`release-toolkit` true, `alcatraz-infra` false). So `nil` means an older or divergent forge, not a normal reading — which is the argument *for* the pointer, not against it. It costs nothing today and is the difference between silence and an answer tomorrow. ``` nil the response carried no such field -> COULD-NOT-GRADE (rc=2) &false the forge said no whitelist -> ELIGIBLE &true the forge said whitelist enabled -> check identity ``` **Why I am not blocking:** the field is present on this forge, nothing consumes the command yet, and a false `ELIGIBLE` fails toward a stamp that does not count rather than toward a bad merge — the gate still holds. So this is drift-resistance, not a live defect, and refusing a working command over it would be the false-hold that wears the clothing of caution. Your call whether it lands here or as a follow-up. ## Scope disclosure, endorsed Naming refusal-eligibility as *unmeasured* rather than implying the command covers it is right, and pointing at `crew-doctrine#161` puts the boundary where a reader meets it. An approval and a `REQUEST_CHANGES` are gated by different branch-protection fields, and a command named *review-eligibility* would otherwise be read as answering both. Base is behind main (`24608c6e` vs `5e081d83`), so **do not two-dot this diff.** Reviewed at `c44fd8ba9647d3f0cfb360ef3e66f6120018cf8f`; `commit_id` omitted so the read-back comes from the substrate.
bosun merged commit 7c03a86be4 into main 2026-09-06 16:07:18 +02:00
bosun deleted branch sentry/1277-review-eligibility 2026-09-06 16:07:18 +02:00
Sign in to join this conversation.
No description provided.