Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ present (except `quest land`'s, which no head carries), and every required
check passed; it names the last required check that passed and is dated when
the head went idle. Each prints once per head, with the check's name
(spaces as `_`) in the actor field and its details URL. Results count as
`quest status` counts them, so neutral and skipped results pass. Each job of a
`quest status` counts them, so neutral and skipped results pass and a rerun
supersedes an earlier run of the same check. Each job of a
workflow a ruleset requires is required.
Requiredness comes from the base branch's protection and rulesets, readable
with read access. A failed optional check prints separately as `check:failed`,
Expand Down Expand Up @@ -455,8 +456,8 @@ comes last, and a missing value prints as `-`:
| `mergeable`, `merge-state`, `review-decision` | GitHub's values, lowercased. |
| `auto-merge` | Method, who enabled it, when, and the head it was enabled on. |
| `merge-queue` | Queue state, position, and when it was enqueued. |
| `landing-stopped` | The reason the latest merge-queue entry, auto-merge, or [`quest land`](#land-a-batch-with-quest-land) queue entry ended (such as `merge_conflict`, `failed_checks`, `manual`, `manually_disabled`, or `merged`), who ended it, and when. |
| `checks` | The head's rollup state and `passed=`, `failing=`, `pending=` counts. |
| `landing-stopped` | The reason the latest merge-queue entry, auto-merge, or [`quest land`](#land-a-batch-with-quest-land) queue entry ended (such as `merge_conflict`, `failed_checks`, `manual`, `manually_disabled`, or `merged`), who ended it, and when; `-` once the PR has entered the merge queue or auto-merge again since. |
| `checks` | `failure` if any check fails, else `pending` if any is pending, else `success`, and `passed=`, `failing=`, `pending=` counts, all from each check's latest run; `-` when the head has no checks. |
| `check` | `failing` or `pending`, `required` or `optional`, the result, details URL, and name. |
| `review` | `current` or `stale` against the head, state, reviewer, time, and URL of each reviewer's latest review. |
| `reacted:<content>` | Actor, time, and the PR or review reacted to, as `quest wait` names reactions. |
Expand All @@ -465,7 +466,11 @@ comes last, and a missing value prints as `-`:
| `starter-hold` | A draft its starter owns: last activity time, PR URL, and `last active` age. Last activity is not when ownership began. |

The node ID is what GraphQL's `addPullRequestReviewThreadReply` and
`resolveReviewThread` take. Neutral and skipped checks pass. A required check
`resolveReviewThread` take. Neutral and skipped checks pass. Only each check's
latest run counts, as for GitHub's mergeability: a rerun, or the same workflow
run again on the head, supersedes an earlier run of that name from the same
app, and a newer commit status supersedes an older one of its context. GitHub's
own rollup state counts the superseded runs too. A required check
is one branch protection or a ruleset requires for this PR. `--json` prints the
same facts as one object with the fields `url`, `state`, `draft`, `head`,
`mergeable`, `mergeState`, `reviewDecision`, `autoMerge`, `mergeQueue`,
Expand Down Expand Up @@ -672,7 +677,8 @@ quest links with `quest resolve`. A failed batch halves the next batch's size,
down to one PR, and each batch that lands grows it back by one; the order and
size persist in the state directory. Conflicting
additions stay queued. A PR whose required checks are still running stays queued
and sits out batches until they pass. If any are still pending two hours after
and sits out batches until they pass; only each check's latest run counts, as in
`quest status`. If any are still pending two hours after
its pick (twice the default `--timeout`), the daemon comments once on that pick,
naming them, since a required check that never reports would hold it silently. The sandbox checks the tip once, without
bisection: success lands one squash commit per PR. A failure, or a base that
Expand Down
3 changes: 2 additions & 1 deletion cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ Changes a workflow may not publish pause the job with a `publication`
decision. The current attempt of a job on an issue or pull request reads it at
`GET /api/runner/context?job=…&attempt=…`, as the App with the requester's
current authority: an issue's text and comments, or a pull request's text,
comments, reviews, unresolved review threads, and checks at its head. A new
comments, reviews, unresolved review threads, and checks at its head, each by
its latest run as `quest status` judges them. A new
plan's attempt reads its `idea` and `context` there instead. [GITHUB.md](GITHUB.md#publication) describes the request, its
validation, and how interrupted publications are reconciled. After a delivery,
the runner reviews the head the job published with the other vendor's model
Expand Down
6 changes: 4 additions & 2 deletions cloudflare/dev/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type Pull = Issue & {
/** Head branch; `quest/<path>` associates the pull request with that quest. */
branch: string;
checks: "SUCCESS" | "FAILURE" | "PENDING" | null;
/** The check runs behind `checks`. */
/** The check runs behind `checks`, oldest first. */
/** `commit` is the one a run reports on; the head by default. */
runs?: { name: string; conclusion: string; url: string; app?: number; context?: boolean; commit?: string }[];
review: "APPROVED" | "CHANGES_REQUESTED" | "REVIEW_REQUIRED" | null;
Expand Down Expand Up @@ -353,11 +353,13 @@ export const ACTIONS_APP = 15368;
/** One page of a head's check runs and commit statuses, as `statusCheckRollup.contexts` pages them. */
function contextPage(pull: Pull, after: unknown) {
const start = typeof after === "string" ? Number(after) : 0;
const all = (pull.runs ?? []).map((run) =>
const all = (pull.runs ?? []).map((run, index) =>
run.context
? { __typename: "StatusContext", context: run.name, state: run.conclusion, targetUrl: run.url }
: {
__typename: "CheckRun",
// GitHub numbers runs as they are created.
databaseId: index + 1,
name: run.name,
status: "COMPLETED",
conclusion: run.conclusion,
Expand Down
16 changes: 11 additions & 5 deletions cloudflare/src/github/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
planInit,
ready,
} from "quest/core";
import { checksState, latestContexts } from "../../../src/github/checks";
import { cleanAutoMergeRefusal, immediateMerge, queueHead } from "../../../src/github/merge";
import { osiApproved } from "../licenses";
import { REVIEW_CHECK, unreviewed } from "./review";
Expand Down Expand Up @@ -1647,12 +1648,12 @@ export class GitHubService {
const reviews = `reviews(first: 50, after: $after) { nodes { author { login } state body submittedAt } ${pageInfo} }`;
const threads = `reviewThreads(first: 100, after: $after) { nodes { id isResolved isOutdated path line comments(first: 50) { ${commentFields} ${pageInfo} } } ${pageInfo} }`;
const contexts = (after: boolean) =>
`contexts(first: 100${after ? ", after: $after" : ""}) { nodes { __typename ... on CheckRun { name status conclusion detailsUrl } ... on StatusContext { context state description targetUrl } } ${pageInfo} }`;
`contexts(first: 100${after ? ", after: $after" : ""}) { nodes { __typename ... on CheckRun { databaseId name status conclusion detailsUrl checkSuite { app { databaseId } } } ... on StatusContext { context state description createdAt targetUrl } } ${pageInfo} }`;
const query =
kind === "issues"
? `issue(number: $number) { number title state body author { login } ${comments} }`
: `pullRequest(number: $number) { number title state body isDraft author { login } baseRefName headRefName headRefOid ${comments} ${reviews} ${threads}
commits(last: 1) { nodes { commit { oid statusCheckRollup { state ${contexts(false)} } } } } }`;
commits(last: 1) { nodes { commit { oid statusCheckRollup { ${contexts(false)} } } } } }`;
type Connection<T> = { nodes: T[]; pageInfo: { hasNextPage: boolean; endCursor: string | null } };
type Comment = { author: { login: string } | null; body: string; createdAt?: string; submittedAt?: string };
type Review = Comment & { state: string };
Expand All @@ -1666,16 +1667,19 @@ export class GitHubService {
};
type Check = {
__typename: string;
databaseId?: number;
checkSuite?: { app: { databaseId: number } | null } | null;
name?: string;
status?: string;
conclusion?: string | null;
detailsUrl?: string | null;
context?: string;
state?: string;
description?: string | null;
createdAt?: string;
targetUrl?: string | null;
};
type Rollup = { state: string; contexts: Connection<Check> };
type Rollup = { contexts: Connection<Check> };
type Item = {
number: number;
title: string;
Expand Down Expand Up @@ -1801,6 +1805,8 @@ export class GitHubService {
"check contexts",
)
: [];
// Only each check's latest run counts, as for mergeability; the rollup's own state counts them all.
const checks = latestContexts(allChecks);
return {
kind: "pull" as const,
...common,
Expand All @@ -1812,8 +1818,8 @@ export class GitHubService {
threads: unresolved,
checks: {
commit: head?.oid ?? null,
state: head?.statusCheckRollup?.state ?? null,
runs: allChecks.map((c) =>
state: head?.statusCheckRollup ? checksState(checks) : null,
runs: checks.map((c) =>
c.__typename === "CheckRun"
? { name: c.name!, state: c.conclusion ?? c.status ?? null, url: c.detailsUrl ?? null }
: {
Expand Down
30 changes: 30 additions & 0 deletions cloudflare/test/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,36 @@ describe("job context", () => {
);
});

it("lists each check's latest run, as on moq-dev/moq#7325", async () => {
// Concurrency cancelled the older workflow run, whose gate failed on purpose; the newer run
// passed. GitHub's rollup state counted both and read FAILURE, yet GitHub merges the head.
const pull = project.pulls[0]!;
pull.runs = [
{ name: "Scope", conclusion: "CANCELLED", url: "https://ci.test/1" },
{ name: "Release gate", conclusion: "FAILURE", url: "https://ci.test/1" },
{ name: "Scope", conclusion: "SUCCESS", url: "https://ci.test/2" },
{ name: "Release gate", conclusion: "SUCCESS", url: "https://ci.test/2" },
{ name: "Release gate", conclusion: "FAILURE", url: "https://other.test", app: 99 },
];
const id = await running({
workflow: "iterate",
target: "pulls/2",
revision: prHead,
branch: "contributor/fix",
trusted: false,
});
const body = (await (await read(id, 1)).json()) as Record<string, any>;
expect(body.checks.state).toBe("FAILURE");
expect(body.checks.runs).toEqual([
{ name: "Scope", state: "SUCCESS", url: "https://ci.test/2" },
{ name: "Release gate", state: "SUCCESS", url: "https://ci.test/2" },
{ name: "Release gate", state: "FAILURE", url: "https://other.test" },
]);
pull.runs.pop();
const passing = (await (await read(id, 1)).json()) as Record<string, any>;
expect(passing.checks.state).toBe("SUCCESS");
});

it.each(["comments", "reviews", "reviewThreads", "thread comments", "checks"])(
"refuses missing or cycling cursors in %s",
async (connection) => {
Expand Down
2 changes: 0 additions & 2 deletions quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ ownership-manifest design:
- [Land through the merge queue with an async fixer](/quest/m0/queue-watcher/README.md) - GitHub's merge queue lands one PR at a time, and a host watcher fixes and re-enqueues each PR it drops
- [Merge through a host gate](/quest/m0/host-land/README.md) - the host's fixer containers, and the frozen batched gate for the repositories not yet switched
- [The land fixer resolves every mechanical failure](/quest/m0/land-fixer/README.md) - workflow pushes and the PR's own CI as context
- [Checks are judged by their latest run](/quest/m0/latest-check-runs.md) - `quest status`, `quest wait`, and the gate count each check's latest run on the head, as GitHub does, so a superseded cancelled or failed run stops failing them or refusing release-plz PRs GitHub would merge
- [Current landing status](/quest/m0/status-landing.md) - `quest status` clears a stale `landing-stopped` reason once the PR is queued again
- [Kix decision UI](/quest/m0/kix/README.md) - planning and implementation through prompts and summaries, run on the maintainer's own runners
- [Codex cloud off](/quest/m0/codex-cloud-off.md) - automatic Codex cloud reviews stop once no open PR relies on them
- [Bun EFAULT report](/quest/m0/bun-efault.md) - report concurrent `rmSync` `EFAULT` on Windows, and drop the workaround once Bun fixes it
Expand Down
108 changes: 0 additions & 108 deletions quest/m0/latest-check-runs.md

This file was deleted.

33 changes: 0 additions & 33 deletions quest/m0/status-landing.md

This file was deleted.

Loading
Loading