ci(proof): skip real-behavior-proof gate for private maintainers (#83418)

* ci(proof): trust maintainer label for private org members

Private organization memberships report author_association=CONTRIBUTOR
on PRs, so the real-behavior-proof gate currently demands proof from
maintainers whose membership is private. The labeler workflow already
applies the 'maintainer' label via the team-membership API (which sees
private members), so treat that label as an equivalent privileged
signal in evaluateRealBehaviorProof.

* ci(proof): drop noisy comments

* ci(proof): check maintainer team membership via GitHub App token

Replace the label-based private-maintainer skip with a direct
getMembershipForUserInOrg call using a minted GitHub App token, mirroring
the pattern labeler.yml already uses for the same lookup. Removes the
race against the labeler workflow and the implicit dependency on the
'maintainer' label having landed first.

The App-token steps are continue-on-error so the gate still runs (using
the existing author_association path) when the App key secrets are
absent or both mints fail.

* ci(proof): narrow App token to members:read

ClawSweeper review #83418: actions/create-github-app-token defaults to
the full installation permission set, but the proof gate only needs the
org-members read scope used by teams.getMembershipForUserInOrg. Set
permission-members: read on both the primary and fallback mint steps.

* docs(changelog): private maintainers skip the real-behavior-proof gate
This commit is contained in:
Dallin Romney
2026-05-19 01:22:59 +09:00
committed by GitHub
parent 9657b8e8ce
commit cf194419c3
5 changed files with 127 additions and 2 deletions

View File

@@ -1,6 +1,9 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { evaluateRealBehaviorProof } from "./real-behavior-proof-policy.mjs";
import {
evaluateRealBehaviorProof,
isMaintainerTeamMember,
} from "./real-behavior-proof-policy.mjs";
function escapeCommandValue(value) {
return String(value)
@@ -23,6 +26,24 @@ if (!pullRequest) {
process.exit(0);
}
const token = process.env.GH_APP_TOKEN;
const org = event.repository?.owner?.login;
const authorLogin = pullRequest.user?.login;
if (token && org && authorLogin) {
try {
if (await isMaintainerTeamMember({ token, org, login: authorLogin })) {
console.log(
`PR author @${authorLogin} is an active member of the ${org}/maintainer team; skipping real behavior proof gate.`,
);
process.exit(0);
}
} catch (error) {
console.warn(
`::warning title=Maintainer membership check failed::${escapeCommandValue(error?.message ?? String(error))}`,
);
}
}
const evaluation = evaluateRealBehaviorProof({ pullRequest });
if (evaluation.passed) {
console.log(evaluation.reason);

View File

@@ -3,6 +3,7 @@ export const PROOF_SUPPLIED_LABEL = "proof: supplied";
export const PROOF_SUFFICIENT_LABEL = "proof: sufficient";
export const NEEDS_REAL_BEHAVIOR_PROOF_LABEL = "triage: needs-real-behavior-proof";
export const MOCK_ONLY_PROOF_LABEL = "triage: mock-only-proof";
export const MAINTAINER_TEAM_SLUG = "maintainer";
const privilegedAuthorAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
@@ -111,6 +112,34 @@ export function hasProofOverride(labels) {
return labelNames(labels).has(PROOF_OVERRIDE_LABEL);
}
export async function isMaintainerTeamMember({
token,
org,
login,
teamSlug = MAINTAINER_TEAM_SLUG,
fetch = globalThis.fetch,
} = {}) {
if (!token || !org || !login) {
return false;
}
const url = `https://api.github.com/orgs/${encodeURIComponent(org)}/teams/${encodeURIComponent(teamSlug)}/memberships/${encodeURIComponent(login)}`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
});
if (response.status === 404) {
return false;
}
if (!response.ok) {
throw new Error(`Team membership lookup failed: ${response.status}`);
}
const body = await response.json();
return body?.state === "active";
}
export function extractRealBehaviorProofSection(body = "") {
// Normalize CRLF → LF so regexes and section slicing see GitHub web-editor PR
// bodies the same way as locally-authored Markdown.