mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
ci: exempt designated authors from readiness declaration
This commit is contained in:
1 parent
55078b9f57
commit
920d634e83
6 files changed
+82
-8
No files matched your search
@@ -106,7 +106,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
|
||||
|
||||
## Author readiness
|
||||
|
||||
<!-- The human owner completes these declarations before requesting review.
|
||||
<!-- The human owner completes these declarations before requesting review,
|
||||
unless the PR author is exempt as listed in CONTRIBUTING.md.
|
||||
The PR author is the owner by default. A different owner must explicitly accept
|
||||
ownership on this PR.
|
||||
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
|
||||
|
||||
@@ -128,8 +128,8 @@ owner to complete. Never attest to human review on their behalf. Draft status
|
||||
is optional; preserve the PR's current draft status unless the user asks to change
|
||||
it. Later pushes do not invalidate the readiness declaration or require another
|
||||
human sign-off. Keep the PR body and affected validation current as work changes.
|
||||
The readiness check validates
|
||||
the filled declaration; CI and automated review results do not establish that
|
||||
The readiness check validates the filled declaration or the author exemption
|
||||
listed in CONTRIBUTING.md; CI and automated review results do not establish that
|
||||
human review occurred, maintainer scope approval, or merge permission.
|
||||
|
||||
<!-- BEGIN:turborepo-agent-rules -->
|
||||
|
||||
+8
-4
@@ -93,7 +93,8 @@ Before requesting maintainer review, the human owner must:
|
||||
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
|
||||
for required checks and real-provider coverage.
|
||||
- Complete the **Author readiness** declaration in the PR template, explicitly
|
||||
stating that the PR is ready for initial review.
|
||||
stating that the PR is ready for initial review, unless the PR author is
|
||||
exempt from the declaration as listed below.
|
||||
|
||||
Keep the PR description concise and current. Explain the problem, observable
|
||||
outcome, approach, and useful evidence; link long logs or put them in optional
|
||||
@@ -114,9 +115,12 @@ validation current as work changes. AI agents must leave human declarations to
|
||||
the human owner and must not attest to a human review on their behalf.
|
||||
|
||||
The `PR readiness` check reports pending for drafts and requires a named human
|
||||
owner and all three template declarations for non-drafts. It checks that the
|
||||
declaration is filled, not whether the code or evidence is correct. Existing PRs
|
||||
can copy the **Author readiness** section from the current template.
|
||||
owner and all three template declarations for non-drafts. PRs authored by
|
||||
@morluto, @kaoru0822-kitauji, @N0zoM1z0, or @gensokyo-dev are exempt from
|
||||
the declaration; their human ownership, review, and evidence responsibilities
|
||||
above still apply. The check identifies authors by GitHub account ID. It checks
|
||||
that the declaration is filled, not whether the code or evidence is correct.
|
||||
Existing PRs can copy the **Author readiness** section from the current template.
|
||||
Open PRs must have distinct head commits because GitHub shares commit statuses.
|
||||
|
||||
Maintainers enable merge enforcement after the workflow is merged: run
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface ReadinessPullRequest {
|
||||
readonly state: "open" | "closed";
|
||||
readonly draft: boolean;
|
||||
readonly body: string | null;
|
||||
readonly user: { readonly id: number } | null;
|
||||
readonly head: { readonly sha: string };
|
||||
readonly base: { readonly ref: string };
|
||||
}
|
||||
@@ -53,7 +54,7 @@ export interface ReadinessGitHub {
|
||||
>;
|
||||
}
|
||||
export function evaluateReadiness(
|
||||
pull: Pick<ReadinessPullRequest, "body" | "draft">,
|
||||
pull: Pick<ReadinessPullRequest, "body" | "draft" | "user">,
|
||||
template: string,
|
||||
): ReadinessResult;
|
||||
export function runReadinessCheck(input: {
|
||||
|
||||
@@ -2,6 +2,14 @@ import { readFile } from "node:fs/promises";
|
||||
|
||||
const result = (state, description) => ({ state, description });
|
||||
|
||||
// GitHub account IDs remain stable when a login changes.
|
||||
const EXEMPT_AUTHOR_IDS = new Set([
|
||||
76467478, // morluto
|
||||
208281322, // kaoru0822-kitauji
|
||||
161784452, // N0zoM1z0
|
||||
342580529, // gensokyo-dev
|
||||
]);
|
||||
|
||||
const OPEN_HEADS_QUERY = `query ReadinessOpenHeads($owner: String!, $name: String!, $after: String) {
|
||||
repository(owner: $owner, name: $name) {
|
||||
pullRequests(first: 100, after: $after, states: OPEN, orderBy: {field: CREATED_AT, direction: ASC}) {
|
||||
@@ -105,6 +113,11 @@ export function evaluateReadiness(pull, template) {
|
||||
"pending",
|
||||
"Draft PR: complete Author readiness before requesting review.",
|
||||
);
|
||||
if (EXEMPT_AUTHOR_IDS.has(pull.user?.id))
|
||||
return result(
|
||||
"success",
|
||||
"PR author is exempt from the readiness declaration.",
|
||||
);
|
||||
const expected = readinessLines(template).flatMap((line) => {
|
||||
const match = /^- \[ \] (.+)$/u.exec(line);
|
||||
return match ? [match[1]] : [];
|
||||
@@ -210,6 +223,7 @@ export async function runReadinessCheck({ github, context, core }) {
|
||||
latest.head.sha !== pull.head.sha ||
|
||||
latest.body !== pull.body ||
|
||||
latest.draft !== pull.draft ||
|
||||
latest.user?.id !== pull.user?.id ||
|
||||
latest.base.ref !== pull.base.ref
|
||||
)
|
||||
verdict = result(
|
||||
|
||||
@@ -30,6 +30,7 @@ const makePull = (
|
||||
state: "open",
|
||||
draft: false,
|
||||
body: readyBody,
|
||||
user: { id: 123456 },
|
||||
head: { sha: "a".repeat(40) },
|
||||
base: { ref: "main" },
|
||||
...changes,
|
||||
@@ -94,6 +95,30 @@ function fixture(changes: Partial<ReadinessPullRequest> = {}) {
|
||||
}
|
||||
|
||||
describe("initial author declaration", () => {
|
||||
it.each([76467478, 208281322, 161784452, 342580529])(
|
||||
"exempts the configured author ID %s, but keeps drafts pending",
|
||||
(id) => {
|
||||
expect(
|
||||
evaluateReadiness(makePull({ user: { id }, body: null }), template),
|
||||
).toMatchObject({
|
||||
state: "success",
|
||||
description: expect.stringContaining("exempt"),
|
||||
});
|
||||
expect(
|
||||
evaluateReadiness(
|
||||
makePull({ user: { id }, body: null, draft: true }),
|
||||
template,
|
||||
).state,
|
||||
).toBe("pending");
|
||||
},
|
||||
);
|
||||
|
||||
it("does not exempt unknown or missing author identities", () => {
|
||||
for (const user of [{ id: 123456 }, null])
|
||||
expect(
|
||||
evaluateReadiness(makePull({ user, body: null }), template).state,
|
||||
).toBe("failure");
|
||||
});
|
||||
it("accepts the filled current template and uppercase checkmarks", () => {
|
||||
expect(evaluateReadiness(makePull(), template).state).toBe("success");
|
||||
expect(
|
||||
@@ -212,6 +237,35 @@ describe("initial author declaration", () => {
|
||||
});
|
||||
|
||||
describe("GitHub status boundary", () => {
|
||||
it.each([76467478, 208281322, 161784452, 342580529])(
|
||||
"publishes a labeled exemption for author ID %s after checking the head",
|
||||
async (id) => {
|
||||
const input = fixture({ user: { id }, body: null });
|
||||
expect(await runReadinessCheck(input)).toMatchObject({
|
||||
state: "success",
|
||||
description: expect.stringContaining("exempt"),
|
||||
});
|
||||
expect(input.paginate).toHaveBeenCalledOnce();
|
||||
expect(input.createCommitStatus).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ state: "success", sha: input.pull.head.sha }),
|
||||
);
|
||||
},
|
||||
);
|
||||
it("does not let an exempt author lend success to another PR sharing the head", async () => {
|
||||
const input = fixture({ user: { id: 76467478 }, body: null });
|
||||
input.paginate.mockResolvedValue([input.pull, makePull({ number: 43 })]);
|
||||
expect((await runReadinessCheck(input))?.state).toBe("failure");
|
||||
});
|
||||
|
||||
it("does not publish an exemption if the author changes during the check", async () => {
|
||||
const input = fixture({ user: { id: 76467478 }, body: null });
|
||||
input.get
|
||||
.mockResolvedValueOnce({ data: input.pull })
|
||||
.mockResolvedValueOnce({
|
||||
data: makePull({ user: { id: 123456 }, body: null }),
|
||||
});
|
||||
expect((await runReadinessCheck(input))?.state).toBe("pending");
|
||||
});
|
||||
it("publishes failure on the actual PR head when declarations are unchecked", async () => {
|
||||
const input = fixture({ body: readyBody.replaceAll("[x]", "[ ]") });
|
||||
expect((await runReadinessCheck(input))?.state).toBe("failure");
|
||||
|
||||
Reference in new issue
Block a user