// A pull request's merge check (novox/hq to-be 45 §9): what the forge's announcer says when a pull // request's head moves, and what it sets as the pull request's status when the mesh says the verdict. // // **Before merge, never after.** Every check the mesh had ran after a merge, on a machine: a manifest the // node-engine refuses (issue 236), an identity a real machine's name made too long (263). So each new head // of an open pull request is announced as `pull.updated`; the controller asks the build seat to check it // against every machine of the mesh's facts; and the verdict comes back as the controller's `checked`, // which this sets as the head commit's status — and, when it is not a pass, as a comment saying why. // // Pure functions here, so they are tested without a forge; index.ts does the asking and the setting. import type { CommitStatus, GiteaPull } from "./client.js"; /** The status context a merge check is kept under: one per commit, the newest replacing the last. */ export const CHECK_CONTEXT = "mesh/merge-gate"; /** What the controller says as `checked` (mesh-controller internal/link, Checked). */ export interface Checked { owner: string; repo: string; number?: number; commit: string; verdict: string; summary: string; report?: string; id: string; on?: string; } /** The heads already announced, keyed `owner/repo#number`, so a restart announces nothing twice. */ export type Announced = Record; /** Which open pull requests have a head not yet announced. A pull request merged or closed is not * open and is never asked about. */ export function headsToAnnounce(full: string, pulls: GiteaPull[], announced: Announced): GiteaPull[] { return pulls.filter((p) => p.state === "open" && !!p.head_sha && announced[`${full}#${p.number}`] !== p.head_sha); } /** The forge's status for a verdict: an error is the forge's `error`, never a success. */ export function statusFor(c: Checked): CommitStatus { const state: CommitStatus["state"] = c.verdict === "pass" ? "success" : c.verdict === "warning" ? "warning" : c.verdict === "fail" ? "failure" : "error"; // The forge keeps a short description; the rest is the comment's. let description = `${c.verdict}: ${c.summary}`.replace(/\s+/g, " ").trim(); if (description.length > 140) description = description.slice(0, 139) + "…"; return { state, context: CHECK_CONTEXT, description }; } /** The comment a verdict that is not a pass leaves on its pull request: the check's own account. */ export function commentFor(c: Checked): string | null { if (c.verdict === "pass") return null; const head = `**Merge check: ${c.verdict.toUpperCase()}** at \`${c.commit.slice(0, 8)}\` — ${c.summary}`; const ran = c.on ? `\n\nRun by the build seat on ${c.on} as \`${c.id}\` (\`builds --log ${c.id}\`).` : ""; const report = c.report ? `\n\n\`\`\`\n${c.report.replace(/```/g, "'''")}\n\`\`\`` : ""; return head + ran + report; }